// Copyright 2018-2022, Collabora, Ltd. // SPDX-License-Identifier: BSL-1.0 /*! * @file * @brief Holds input related functions. * @author Jakob Bornecrantz * @ingroup oxr_main */ #include "util/u_debug.h" #include "util/u_time.h" #include "util/u_misc.h" #include "xrt/xrt_compiler.h" #include "oxr_objects.h" #include "oxr_logger.h" #include "oxr_handle.h" #include "oxr_two_call.h" #include "oxr_input_transform.h" #include "oxr_subaction.h" #include #include #include #include #include /* * * Pre declare functions. * */ static void oxr_session_get_action_set_attachment(struct oxr_session *sess, XrActionSet actionSet, struct oxr_action_set_attachment **act_set_attached, struct oxr_action_set **act_set); static void oxr_session_get_action_attachment(struct oxr_session *sess, uint32_t act_key, struct oxr_action_attachment **out_act_attached); static void oxr_action_cache_update(struct oxr_logger *log, struct oxr_session *sess, uint32_t countActionSets, const XrActiveActionSet *actionSets, struct oxr_action_attachment *act_attached, struct oxr_action_cache *cache, int64_t time, struct oxr_subaction_paths *subaction_path, bool select); static void oxr_action_attachment_update(struct oxr_logger *log, struct oxr_session *sess, uint32_t countActionSets, const XrActiveActionSet *actionSets, struct oxr_action_attachment *act_attached, int64_t time, struct oxr_subaction_paths subaction_paths); static void oxr_action_bind_io(struct oxr_logger *log, struct oxr_sink_logger *slog, struct oxr_session *sess, struct oxr_action *act, struct oxr_action_cache *cache, struct oxr_interaction_profile *profile, enum oxr_subaction_path subaction_path); /* * * Action attachment functions * */ /*! * De-initialize/de-allocate all dynamic members of @ref oxr_action_cache * @private @memberof oxr_action_cache */ static void oxr_action_cache_teardown(struct oxr_action_cache *cache) { // Clean up input transforms for (uint32_t i = 0; i < cache->input_count; i++) { struct oxr_action_input *action_input = &cache->inputs[i]; oxr_input_transform_destroy(&(action_input->transforms)); action_input->transform_count = 0; } free(cache->inputs); cache->inputs = NULL; free(cache->outputs); cache->outputs = NULL; } /*! * Tear down an action attachment struct. * * Does not deallocate the struct itself. * * @public @memberof oxr_action_attachment */ static void oxr_action_attachment_teardown(struct oxr_action_attachment *act_attached) { struct oxr_session *sess = act_attached->sess; u_hashmap_int_erase(sess->act_attachments_by_key, act_attached->act_key); #define CACHE_TEARDOWN(X) oxr_action_cache_teardown(&(act_attached->X)); OXR_FOR_EACH_SUBACTION_PATH(CACHE_TEARDOWN) #undef CACHE_TEARDOWN // Unref this action's refcounted data oxr_refcounted_unref(&act_attached->act_ref->base); } /*! * Set up an action attachment struct. * * @public @memberof oxr_action_attachment */ static XrResult oxr_action_attachment_init(struct oxr_logger *log, struct oxr_action_set_attachment *act_set_attached, struct oxr_action_attachment *act_attached, struct oxr_action *act) { struct oxr_session *sess = act_set_attached->sess; act_attached->sess = sess; act_attached->act_set_attached = act_set_attached; u_hashmap_int_insert(sess->act_attachments_by_key, act->act_key, act_attached); // Reference this action's refcounted data act_attached->act_ref = act->data; oxr_refcounted_ref(&act_attached->act_ref->base); // Copy this for efficiency. act_attached->act_key = act->act_key; return XR_SUCCESS; } /* * * Action set attachment functions * */ /*! * @public @memberof oxr_action_set_attachment */ static XrResult oxr_action_set_attachment_init(struct oxr_logger *log, struct oxr_session *sess, struct oxr_action_set *act_set, struct oxr_action_set_attachment *act_set_attached) { act_set_attached->sess = sess; // Reference this action set's refcounted data act_set_attached->act_set_ref = act_set->data; oxr_refcounted_ref(&act_set_attached->act_set_ref->base); u_hashmap_int_insert(sess->act_sets_attachments_by_key, act_set->act_set_key, act_set_attached); // Copy this for efficiency. act_set_attached->act_set_key = act_set->act_set_key; return XR_SUCCESS; } void oxr_action_set_attachment_teardown(struct oxr_action_set_attachment *act_set_attached) { for (size_t i = 0; i < act_set_attached->action_attachment_count; ++i) { oxr_action_attachment_teardown(&(act_set_attached->act_attachments[i])); } free(act_set_attached->act_attachments); act_set_attached->act_attachments = NULL; act_set_attached->action_attachment_count = 0; struct oxr_session *sess = act_set_attached->sess; u_hashmap_int_erase(sess->act_sets_attachments_by_key, act_set_attached->act_set_key); // Unref this action set's refcounted data oxr_refcounted_unref(&act_set_attached->act_set_ref->base); } /* * * Action set functions * */ static void oxr_action_set_ref_destroy_cb(struct oxr_refcounted *orc) { struct oxr_action_set_ref *act_set_ref = (struct oxr_action_set_ref *)orc; u_hashset_destroy(&act_set_ref->actions.name_store); u_hashset_destroy(&act_set_ref->actions.loc_store); free(act_set_ref); } static XrResult oxr_action_set_destroy_cb(struct oxr_logger *log, struct oxr_handle_base *hb) { struct oxr_action_set *act_set = (struct oxr_action_set *)hb; oxr_refcounted_unref(&act_set->data->base); act_set->data = NULL; if (act_set->name_item != NULL) { u_hashset_erase_item(act_set->inst->action_sets.name_store, act_set->name_item); free(act_set->name_item); act_set->name_item = NULL; } if (act_set->loc_item != NULL) { u_hashset_erase_item(act_set->inst->action_sets.loc_store, act_set->loc_item); free(act_set->loc_item); act_set->loc_item = NULL; } free(act_set); return XR_SUCCESS; } XrResult oxr_action_set_create(struct oxr_logger *log, struct oxr_instance *inst, const XrActionSetCreateInfo *createInfo, struct oxr_action_set **out_act_set) { // Mod music for all! static uint32_t key_gen = 1; int h_ret; struct oxr_action_set *act_set = NULL; OXR_ALLOCATE_HANDLE_OR_RETURN(log, act_set, OXR_XR_DEBUG_ACTIONSET, oxr_action_set_destroy_cb, &inst->handle); struct oxr_action_set_ref *act_set_ref = U_TYPED_CALLOC(struct oxr_action_set_ref); act_set_ref->base.destroy = oxr_action_set_ref_destroy_cb; oxr_refcounted_ref(&act_set_ref->base); act_set->data = act_set_ref; act_set_ref->act_set_key = key_gen++; act_set->act_set_key = act_set_ref->act_set_key; act_set->inst = inst; h_ret = u_hashset_create(&act_set_ref->actions.name_store); if (h_ret != 0) { oxr_handle_destroy(log, &act_set->handle); return oxr_error(log, XR_ERROR_RUNTIME_FAILURE, "Failed to create name_store hashset"); } h_ret = u_hashset_create(&act_set_ref->actions.loc_store); if (h_ret != 0) { oxr_handle_destroy(log, &act_set->handle); return oxr_error(log, XR_ERROR_RUNTIME_FAILURE, "Failed to create loc_store hashset"); } snprintf(act_set_ref->name, sizeof(act_set_ref->name), "%s", createInfo->actionSetName); u_hashset_create_and_insert_str_c(inst->action_sets.name_store, createInfo->actionSetName, &act_set->name_item); u_hashset_create_and_insert_str_c(inst->action_sets.loc_store, createInfo->localizedActionSetName, &act_set->loc_item); act_set_ref->priority = createInfo->priority; *out_act_set = act_set; return XR_SUCCESS; } /* * * Action functions * */ static void oxr_action_ref_destroy_cb(struct oxr_refcounted *orc) { struct oxr_action_ref *act_ref = (struct oxr_action_ref *)orc; free(act_ref); } static XrResult oxr_action_destroy_cb(struct oxr_logger *log, struct oxr_handle_base *hb) { struct oxr_action *act = (struct oxr_action *)hb; oxr_refcounted_unref(&act->data->base); act->data = NULL; if (act->name_item != NULL) { u_hashset_erase_item(act->act_set->data->actions.name_store, act->name_item); free(act->name_item); act->name_item = NULL; } if (act->loc_item != NULL) { u_hashset_erase_item(act->act_set->data->actions.loc_store, act->loc_item); free(act->loc_item); act->loc_item = NULL; } free(act); return XR_SUCCESS; } XrResult oxr_action_create(struct oxr_logger *log, struct oxr_action_set *act_set, const XrActionCreateInfo *createInfo, struct oxr_action **out_act) { struct oxr_instance *inst = act_set->inst; struct oxr_subaction_paths subaction_paths = {0}; // Mod music for all! static uint32_t key_gen = 1; if (!oxr_classify_sub_action_paths(log, inst, createInfo->countSubactionPaths, createInfo->subactionPaths, &subaction_paths)) { return XR_ERROR_PATH_UNSUPPORTED; } struct oxr_action *act = NULL; OXR_ALLOCATE_HANDLE_OR_RETURN(log, act, OXR_XR_DEBUG_ACTION, oxr_action_destroy_cb, &act_set->handle); struct oxr_action_ref *act_ref = U_TYPED_CALLOC(struct oxr_action_ref); act_ref->base.destroy = oxr_action_ref_destroy_cb; oxr_refcounted_ref(&act_ref->base); act->data = act_ref; act_ref->act_key = key_gen++; act->act_key = act_ref->act_key; act->act_set = act_set; act_ref->subaction_paths = subaction_paths; act_ref->action_type = createInfo->actionType; snprintf(act_ref->name, sizeof(act_ref->name), "%s", createInfo->actionName); u_hashset_create_and_insert_str_c(act_set->data->actions.name_store, createInfo->actionName, &act->name_item); u_hashset_create_and_insert_str_c(act_set->data->actions.loc_store, createInfo->localizedActionName, &act->loc_item); *out_act = act; return XR_SUCCESS; } /* * * "Exported" helper functions. * */ bool oxr_classify_sub_action_paths(struct oxr_logger *log, struct oxr_instance *inst, uint32_t subaction_path_count, const XrPath *subaction_paths, struct oxr_subaction_paths *subaction_paths_out) { const char *str = NULL; size_t length = 0; bool ret = true; // Reset the subaction_paths completely. U_ZERO(subaction_paths_out); if (subaction_path_count == 0) { subaction_paths_out->any = true; return ret; } for (uint32_t i = 0; i < subaction_path_count; i++) { XrPath path = subaction_paths[i]; #define IDENTIFY_PATH(X) \ else if (path == inst->path_cache.X) \ { \ subaction_paths_out->X = true; \ } if (path == XR_NULL_PATH) { subaction_paths_out->any = true; } OXR_FOR_EACH_VALID_SUBACTION_PATH(IDENTIFY_PATH) else { oxr_path_get_string(log, inst, path, &str, &length); oxr_warn(log, " unrecognized sub action path '%s'", str); ret = false; } #undef IDENTIFY_PATH } return ret; } XrResult oxr_action_get_pose_input(struct oxr_session *sess, uint32_t act_key, const struct oxr_subaction_paths *subaction_paths_ptr, struct oxr_action_input **out_input) { struct oxr_action_attachment *act_attached = NULL; oxr_session_get_action_attachment(sess, act_key, &act_attached); if (act_attached == NULL) { return XR_SUCCESS; } struct oxr_subaction_paths subaction_paths = *subaction_paths_ptr; if (subaction_paths.any) { subaction_paths = act_attached->any_pose_subaction_path; } // Priority of inputs. #define GET_POSE_INPUT(X) \ if (act_attached->X.current.active && subaction_paths.X) { \ *out_input = act_attached->X.inputs; \ return XR_SUCCESS; \ } OXR_FOR_EACH_VALID_SUBACTION_PATH(GET_POSE_INPUT) // plus a fallback invocation for user GET_POSE_INPUT(user) #undef GET_POSE_INPUT return XR_SUCCESS; } /* * * Not so hack functions. * */ static bool do_inputs(struct oxr_binding *binding_point, struct xrt_device *xdev, struct xrt_binding_profile *xbp, XrPath matched_path, struct oxr_action_input inputs[OXR_MAX_BINDINGS_PER_ACTION], uint32_t *input_count) { enum xrt_input_name name = 0; if (xbp == NULL) { name = binding_point->input; } else { for (size_t i = 0; i < xbp->input_count; i++) { if (binding_point->input != xbp->inputs[i].from) { continue; } // We have found a device mapping. name = xbp->inputs[i].device; break; } // Didn't find a mapping. if (name == 0) { return false; } } struct xrt_input *input = NULL; if (oxr_xdev_find_input(xdev, name, &input)) { uint32_t index = (*input_count)++; inputs[index].input = input; inputs[index].xdev = xdev; inputs[index].bound_path = matched_path; if (binding_point->dpad_activate != 0) { struct xrt_input *dpad_activate = NULL; if (!oxr_xdev_find_input(xdev, binding_point->dpad_activate, &dpad_activate)) { return false; } inputs[index].dpad_activate_name = binding_point->dpad_activate; inputs[index].dpad_activate = dpad_activate; } return true; } return false; } static bool do_outputs(struct oxr_binding *binding_point, struct xrt_device *xdev, struct xrt_binding_profile *xbp, XrPath matched_path, struct oxr_action_output outputs[OXR_MAX_BINDINGS_PER_ACTION], uint32_t *output_count) { enum xrt_output_name name = 0; if (xbp == NULL) { name = binding_point->output; } else { for (size_t i = 0; i < xbp->output_count; i++) { if (binding_point->output != xbp->outputs[i].from) { continue; } // We have found a device mapping. name = xbp->outputs[i].device; break; } // Didn't find a mapping. if (name == 0) { return false; } } struct xrt_output *output = NULL; if (oxr_xdev_find_output(xdev, name, &output)) { uint32_t index = (*output_count)++; outputs[index].name = name; outputs[index].xdev = xdev; outputs[index].bound_path = matched_path; return true; } return false; } /*! * Delegate to @ref do_outputs or @ref do_inputs depending on whether the action * is output or input. */ static bool do_io_bindings(struct oxr_binding *binding_point, struct oxr_action *act, struct xrt_device *xdev, struct xrt_binding_profile *xbp, XrPath matched_path, struct oxr_action_input inputs[OXR_MAX_BINDINGS_PER_ACTION], uint32_t *input_count, struct oxr_action_output outputs[OXR_MAX_BINDINGS_PER_ACTION], uint32_t *output_count) { if (act->data->action_type == XR_ACTION_TYPE_VIBRATION_OUTPUT) { return do_outputs( // binding_point, // xdev, // xbp, // matched_path, // outputs, // output_count); // } return do_inputs( // binding_point, // xdev, // xbp, // matched_path, // inputs, // input_count); // } static struct xrt_binding_profile * get_matching_binding_profile(struct oxr_interaction_profile *profile, struct xrt_device *xdev) { for (size_t i = 0; i < xdev->binding_profile_count; i++) { if (xdev->binding_profiles[i].name == profile->xname) { return &xdev->binding_profiles[i]; } } return NULL; } static XrPath get_matched_xrpath(struct oxr_binding *b, struct oxr_action *act) { XrPath preferred_path = XR_NULL_PATH; for (uint32_t i = 0; i < b->key_count; i++) { if (b->keys[i] == act->act_key) { uint32_t preferred_path_index = XR_NULL_PATH; preferred_path_index = b->preferred_binding_path_index[i]; preferred_path = b->paths[preferred_path_index]; break; } } return preferred_path; } static void get_binding(struct oxr_logger *log, struct oxr_sink_logger *slog, struct oxr_session *sess, struct oxr_action *act, struct oxr_interaction_profile *profile, enum oxr_subaction_path subaction_path, struct oxr_action_input inputs[OXR_MAX_BINDINGS_PER_ACTION], uint32_t *input_count, struct oxr_action_output outputs[OXR_MAX_BINDINGS_PER_ACTION], uint32_t *output_count) { struct xrt_device *xdev = NULL; struct oxr_binding *binding_points[OXR_MAX_BINDINGS_PER_ACTION]; const char *profile_str; const char *user_path_str; size_t length; //! @todo This probably falls on its head if the application doesn't use //! sub action paths. switch (subaction_path) { #define PATH_CASE(NAME, NAMECAPS, PATH) \ case OXR_SUB_ACTION_PATH_##NAMECAPS: \ user_path_str = PATH; \ xdev = GET_XDEV_BY_ROLE(sess->sys, NAME); \ break; OXR_FOR_EACH_VALID_SUBACTION_PATH_DETAILED(PATH_CASE) #undef PATH_CASE // Manually-coded fallback for not-really-valid /user case OXR_SUB_ACTION_PATH_USER: user_path_str = "/user"; xdev = NULL; break; default: break; } oxr_slog(slog, "\tFor: %s\n", user_path_str); if (xdev == NULL) { oxr_slog(slog, "\t\tNo xdev!\n"); return; } if (profile == NULL) { oxr_slog(slog, "\t\tNo profile!\n"); return; } oxr_path_get_string(log, sess->sys->inst, profile->path, &profile_str, &length); oxr_slog(slog, "\t\tProfile: %s\n", profile_str); /*! * Lookup device binding that matches the interactive profile, this * is used as a fallback should the device not match the interactive * profile. This allows the device to provide a mapping from one device * to itself. */ struct xrt_binding_profile *xbp = get_matching_binding_profile(profile, xdev); // No point in proceeding here without either. if (profile->xname != xdev->name && xbp == NULL) { oxr_slog(slog, "\t\t\tProfile not for device and no xbp fallback!\n"); return; } size_t num = 0; oxr_binding_find_bindings_from_key(log, profile, act->act_key, binding_points, &num); if (num == 0) { oxr_slog(slog, "\t\t\tNo bindings!\n"); return; } for (size_t i = 0; i < num; i++) { const char *str = NULL; struct oxr_binding *binding_point = binding_points[i]; XrPath matched_path = get_matched_xrpath(binding_point, act); oxr_path_get_string(log, sess->sys->inst, matched_path, &str, &length); oxr_slog(slog, "\t\t\tBinding: %s\n", str); if (binding_point->subaction_path != subaction_path) { oxr_slog(slog, "\t\t\t\tRejected! (SUB PATH)\n"); continue; } bool found = do_io_bindings( // binding_point, // act, // xdev, // xbp, // matched_path, // inputs, // input_count, // outputs, // output_count); // if (found) { if (xbp == NULL) { oxr_slog(slog, "\t\t\t\tBound (xdev)!\n"); } else { oxr_slog(slog, "\t\t\t\tBound (xbp)!\n"); } continue; } if (xbp == NULL) { oxr_slog(slog, "\t\t\t\tRejected! (NO XDEV NAME)\n"); } else { oxr_slog(slog, "\t\t\t\tRejected! (NO XBINDING)\n"); } } } struct oxr_profiles_per_subaction { #define PROFILE_MEMBER(X) struct oxr_interaction_profile *X; OXR_FOR_EACH_VALID_SUBACTION_PATH(PROFILE_MEMBER) #undef PROFILE_MEMBER }; /*! * @public @memberof oxr_action_attachment */ static XrResult oxr_action_attachment_bind(struct oxr_logger *log, struct oxr_action_attachment *act_attached, struct oxr_action *act, const struct oxr_profiles_per_subaction *profiles) { struct oxr_sink_logger slog = {0}; struct oxr_action_ref *act_ref = act->data; struct oxr_session *sess = act_attached->sess; // Start logging into a single buffer. oxr_slog(&slog, ": Binding %s/%s\n", act->act_set->data->name, act_ref->name); if (act_ref->subaction_paths.user || act_ref->subaction_paths.any) { #if 0 oxr_action_bind_io(log, &slog, sess, act, &act_attached->user, user, OXR_SUB_ACTION_PATH_USER); #endif } #define BIND_SUBACTION(NAME, NAME_CAPS, PATH) \ if (act_ref->subaction_paths.NAME || act_ref->subaction_paths.any) { \ oxr_action_bind_io(log, &slog, sess, act, &act_attached->NAME, profiles->NAME, \ OXR_SUB_ACTION_PATH_##NAME_CAPS); \ } OXR_FOR_EACH_VALID_SUBACTION_PATH_DETAILED(BIND_SUBACTION) #undef BIND_SUBACTION /*! * The any sub path is special cased for poses, it binds to one sub path * and sticks with it. */ if (act_ref->action_type == XR_ACTION_TYPE_POSE_INPUT) { #define POSE_ANY(NAME) \ if ((act_ref->subaction_paths.NAME || act_ref->subaction_paths.any) && act_attached->NAME.input_count > 0) { \ act_attached->any_pose_subaction_path.NAME = true; \ oxr_slog(&slog, "\tFor: \n\t\tBinding any pose to " #NAME ".\n"); \ } else OXR_FOR_EACH_VALID_SUBACTION_PATH(POSE_ANY) #undef POSE_ANY { oxr_slog(&slog, "\tFor: \n\t\tNo active sub paths for " "the any pose!\n"); } } oxr_slog(&slog, "\tDone"); // Also frees all data. if (sess->sys->inst->debug_bindings) { oxr_log_slog(log, &slog); } else { oxr_slog_cancel(&slog); } return XR_SUCCESS; } static void oxr_action_cache_stop_output(struct oxr_logger *log, struct oxr_session *sess, struct oxr_action_cache *cache) { // Set this as stopped. cache->stop_output_time = 0; union xrt_output_value value = {0}; for (uint32_t i = 0; i < cache->output_count; i++) { struct oxr_action_output *output = &cache->outputs[i]; struct xrt_device *xdev = output->xdev; xrt_device_set_output(xdev, output->name, &value); } } static bool oxr_input_is_input_for_cache(struct oxr_action_input *action_input, struct oxr_action_cache *cache) { for (size_t i = 0; i < cache->input_count; i++) { if (action_input->bound_path == cache->inputs[i].bound_path) { return true; } } return false; } static bool oxr_input_is_bound_in_act_set(struct oxr_action_input *action_input, struct oxr_action_set_attachment *act_set_attached) { for (size_t i = 0; i < act_set_attached->action_attachment_count; i++) { struct oxr_action_attachment *act_attached = &act_set_attached->act_attachments[i]; #define ACCUMULATE_PATHS(X) \ if (oxr_input_is_input_for_cache(action_input, &act_attached->X)) { \ return true; \ } OXR_FOR_EACH_SUBACTION_PATH(ACCUMULATE_PATHS) #undef ACCUMULATE_PATHS } return false; } static bool oxr_input_supressed(struct oxr_session *sess, uint32_t countActionSets, const XrActiveActionSet *actionSets, struct oxr_subaction_paths *subaction_path, struct oxr_action_attachment *act_attached, struct oxr_action_input *action_input) { struct oxr_action_set_ref *act_set_ref = act_attached->act_set_attached->act_set_ref; uint32_t priority = act_set_ref->priority; // find sources that are bound to an action in a set with higher prio for (uint32_t i = 0; i < countActionSets; i++) { XrActionSet set = actionSets[i].actionSet; struct oxr_action_set *other_act_set = NULL; struct oxr_action_set_attachment *other_act_set_attached = NULL; oxr_session_get_action_set_attachment(sess, set, &other_act_set_attached, &other_act_set); if (other_act_set_attached == NULL) { continue; } /* skip the action set that the current action is in */ if (other_act_set_attached->act_set_ref == act_set_ref) { continue; } /* input may be suppressed by action set with higher prio */ if (other_act_set_attached->act_set_ref->priority <= priority) { continue; } /* Currently updated input source with subactionpath X can be * suppressed, if input source also occurs in action set with * higher priority if * - high prio set syncs w/ ANY subactionpath or * - high prio set syncs w/ subactionpath matching this input * subactionpath */ bool relevant_subactionpath = other_act_set_attached->requested_subaction_paths.any; #define ACCUMULATE_PATHS(X) \ relevant_subactionpath |= (other_act_set_attached->requested_subaction_paths.X && subaction_path->X); OXR_FOR_EACH_SUBACTION_PATH(ACCUMULATE_PATHS) #undef ACCUMULATE_PATHS if (!relevant_subactionpath) { continue; } if (oxr_input_is_bound_in_act_set(action_input, other_act_set_attached)) { return true; } } return false; } static bool oxr_input_combine_input(struct oxr_session *sess, uint32_t countActionSets, const XrActiveActionSet *actionSets, struct oxr_action_attachment *act_attached, struct oxr_subaction_paths *subaction_path, struct oxr_action_cache *cache, struct oxr_input_value_tagged *out_input, int64_t *timestamp, bool *is_active) { struct oxr_action_input *inputs = cache->inputs; size_t input_count = cache->input_count; if (input_count == 0) { *is_active = false; return true; } bool any_active = false; struct oxr_input_value_tagged res = {0}; int64_t res_timestamp = inputs[0].input->timestamp; for (size_t i = 0; i < input_count; i++) { struct oxr_action_input *action_input = &(inputs[i]); struct xrt_input *input = action_input->input; // suppress input if it is also bound to action in set with // higher priority if (oxr_input_supressed(sess, countActionSets, actionSets, subaction_path, act_attached, action_input)) { continue; } if (input->active) { any_active = true; } else { continue; } struct oxr_input_value_tagged raw_input = { .type = XRT_GET_INPUT_TYPE(input->name), .value = input->value, }; struct oxr_input_value_tagged transformed = {0}; if (!oxr_input_transform_process(action_input->transforms, action_input->transform_count, &raw_input, &transformed)) { // We couldn't transform, how strange. Reset all state. // At this level we don't know what action this is, etc. // so a warning message isn't very helpful. return false; } // at this stage type should be "compatible" to action res.type = transformed.type; switch (transformed.type) { case XRT_INPUT_TYPE_BOOLEAN: res.value.boolean |= transformed.value.boolean; /* Special case bool: all bool inputs are combined with * OR. The action only changes to true on the earliest * input that sets it to true, and to false on the * latest input that is false. */ if (res.value.boolean && transformed.value.boolean && input->timestamp < res_timestamp) { res_timestamp = input->timestamp; } else if (!res.value.boolean && !transformed.value.boolean && input->timestamp > res_timestamp) { res_timestamp = input->timestamp; } break; case XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE: case XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE: if (fabsf(transformed.value.vec1.x) > fabsf(res.value.vec1.x)) { res.value.vec1.x = transformed.value.vec1.x; res_timestamp = input->timestamp; } break; case XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE: { float res_sq = res.value.vec2.x * res.value.vec2.x + res.value.vec2.y * res.value.vec2.y; float trans_sq = transformed.value.vec2.x * transformed.value.vec2.x + transformed.value.vec2.y * transformed.value.vec2.y; if (trans_sq > res_sq) { res.value.vec2 = transformed.value.vec2; res_timestamp = input->timestamp; } } break; case XRT_INPUT_TYPE_VEC3_MINUS_ONE_TO_ONE: // OpenXR has no vec3 right now. break; case XRT_INPUT_TYPE_POSE: // shouldn't be possible to get here break; case XRT_INPUT_TYPE_HAND_TRACKING: // shouldn't be possible to get here break; } } *is_active = any_active; *out_input = res; *timestamp = res_timestamp; return true; } /*! * Called during xrSyncActions. * * @private @memberof oxr_action_cache */ static void oxr_action_cache_update(struct oxr_logger *log, struct oxr_session *sess, uint32_t countActionSets, const XrActiveActionSet *actionSets, struct oxr_action_attachment *act_attached, struct oxr_action_cache *cache, int64_t time, struct oxr_subaction_paths *subaction_path, bool selected) { struct oxr_action_state last = cache->current; if (!selected) { if (cache->stop_output_time > 0) { oxr_action_cache_stop_output(log, sess, cache); } U_ZERO(&cache->current); return; } struct oxr_input_value_tagged combined; int64_t timestamp; bool is_active; /* a cache can only have outputs or inputs, not both */ if (cache->output_count > 0) { cache->current.active = true; if (cache->stop_output_time < time) { oxr_action_cache_stop_output(log, sess, cache); } } else if (cache->input_count > 0) { if (!oxr_input_combine_input(sess, countActionSets, actionSets, act_attached, subaction_path, cache, &combined, ×tamp, &is_active)) { oxr_log(log, "Failed to get/combine input values '%s'", act_attached->act_ref->name); return; } // If the input is not active signal that. if (!is_active) { // Reset all state. U_ZERO(&cache->current); return; } // Signal that the input is active, always set just to be sure. cache->current.active = true; bool changed = false; switch (combined.type) { case XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE: case XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE: { changed = (combined.value.vec1.x != last.value.vec1.x); cache->current.value.vec1.x = combined.value.vec1.x; break; } case XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE: { changed = (combined.value.vec2.x != last.value.vec2.x) || (combined.value.vec2.y != last.value.vec2.y); cache->current.value.vec2.x = combined.value.vec2.x; cache->current.value.vec2.y = combined.value.vec2.y; break; } #if 0 // Untested, we have no VEC3 input. case XRT_INPUT_TYPE_VEC3_MINUS_ONE_TO_ONE: { changed = (combined.value.vec3.x != last.vec3.x) || (combined.value.vec3.y != last.vec3.y) || (combined.value.vec3.z != last.vec3.z); cache->current.vec3.x = combined.value.vec3.x; cache->current.vec3.y = combined.value.vec3.y; cache->current.vec3.z = combined.value.vec3.z; break; } #endif case XRT_INPUT_TYPE_BOOLEAN: { changed = (combined.value.boolean != last.value.boolean); cache->current.value.boolean = combined.value.boolean; break; } case XRT_INPUT_TYPE_POSE: return; default: // Should not end up here. assert(false); } if (last.active && changed) { // We were active last sync, and we've changed since // then cache->current.timestamp = timestamp; cache->current.changed = true; } else if (last.active) { // We were active last sync, but we haven't changed // since then. cache->current.timestamp = last.timestamp; cache->current.changed = false; } else { // We are active now but weren't active last time. cache->current.timestamp = timestamp; cache->current.changed = false; } } } #define BOOL_CHECK(NAME) \ if (act_attached->NAME.current.active) { \ active |= true; \ value |= act_attached->NAME.current.value.boolean; \ timestamp = act_attached->NAME.current.timestamp; \ } #define VEC1_CHECK(NAME) \ if (act_attached->NAME.current.active) { \ active |= true; \ if (value < act_attached->NAME.current.value.vec1.x) { \ value = act_attached->NAME.current.value.vec1.x; \ timestamp = act_attached->NAME.current.timestamp; \ } \ } #define VEC2_CHECK(NAME) \ if (act_attached->NAME.current.active) { \ active |= true; \ float curr_x = act_attached->NAME.current.value.vec2.x; \ float curr_y = act_attached->NAME.current.value.vec2.y; \ float curr_d = curr_x * curr_x + curr_y * curr_y; \ if (distance < curr_d) { \ x = curr_x; \ y = curr_y; \ distance = curr_d; \ timestamp = act_attached->NAME.current.timestamp; \ } \ } /*! * Called during each xrSyncActions. * * @private @memberof oxr_action_attachment */ static void oxr_action_attachment_update(struct oxr_logger *log, struct oxr_session *sess, uint32_t countActionSets, const XrActiveActionSet *actionSets, struct oxr_action_attachment *act_attached, int64_t time, struct oxr_subaction_paths subaction_paths) { // This really shouldn't be happening. if (act_attached == NULL) { return; } //! @todo "/user" sub-action path. #define UPDATE_SELECT(X) \ struct oxr_subaction_paths subaction_paths_##X = {0}; \ subaction_paths_##X.X = true; \ bool select_##X = subaction_paths.X || subaction_paths.any; \ oxr_action_cache_update(log, sess, countActionSets, actionSets, act_attached, &act_attached->X, time, \ &subaction_paths_##X, select_##X); OXR_FOR_EACH_VALID_SUBACTION_PATH(UPDATE_SELECT) #undef UPDATE_SELECT /* * Any state. */ struct oxr_action_state last = act_attached->any_state; bool active = false; bool changed = false; XrTime timestamp = 0; switch (act_attached->act_ref->action_type) { case XR_ACTION_TYPE_BOOLEAN_INPUT: { bool value = false; OXR_FOR_EACH_VALID_SUBACTION_PATH(BOOL_CHECK) changed = (last.value.boolean != value); act_attached->any_state.value.boolean = value; break; } case XR_ACTION_TYPE_FLOAT_INPUT: { // Smaller than any possible real value float value = -2.0f; // NOLINT OXR_FOR_EACH_VALID_SUBACTION_PATH(VEC1_CHECK) changed = last.value.vec1.x != value; act_attached->any_state.value.vec1.x = value; break; } case XR_ACTION_TYPE_VECTOR2F_INPUT: { float x = 0.0f; float y = 0.0f; float distance = -1.0f; OXR_FOR_EACH_VALID_SUBACTION_PATH(VEC2_CHECK) changed = (last.value.vec2.x != x) || (last.value.vec2.y != y); act_attached->any_state.value.vec2.x = x; act_attached->any_state.value.vec2.y = y; break; } default: case XR_ACTION_TYPE_POSE_INPUT: case XR_ACTION_TYPE_VIBRATION_OUTPUT: // Nothing to do //! @todo You sure? return; } if (!active) { U_ZERO(&act_attached->any_state); } else if (last.active && changed) { act_attached->any_state.timestamp = timestamp; act_attached->any_state.changed = true; act_attached->any_state.active = true; } else if (last.active) { act_attached->any_state.timestamp = last.timestamp; act_attached->any_state.changed = false; act_attached->any_state.active = true; } else { act_attached->any_state.timestamp = timestamp; act_attached->any_state.changed = false; act_attached->any_state.active = true; } } /*! * Try to produce a transform chain to convert the available input into the * desired input type. * * Populates @p action_input->transforms and @p action_input->transform_count on * success. * * @returns false if it could not, true if it could */ static bool oxr_action_populate_input_transform(struct oxr_logger *log, struct oxr_sink_logger *slog, struct oxr_session *sess, struct oxr_action *act, struct oxr_action_input *action_input) { assert(action_input->transforms == NULL); assert(action_input->transform_count == 0); const char *str; size_t length; oxr_path_get_string(log, sess->sys->inst, action_input->bound_path, &str, &length); enum xrt_input_type t = XRT_GET_INPUT_TYPE(action_input->input->name); return oxr_input_transform_create_chain(log, slog, t, act->data->action_type, act->data->name, str, &action_input->transforms, &action_input->transform_count); } /*! * Find dpad settings in @p dpad_entry whose binding path * is a prefix of @p bound_path_string. * * @returns true if settings were found and written to @p out_dpad_settings */ static bool find_matching_dpad(struct oxr_logger *log, struct oxr_instance *inst, struct oxr_dpad_entry *dpad_entry, const char *bound_path_string, struct oxr_dpad_binding_modification **out_dpad_binding) { if (dpad_entry != NULL) { for (uint32_t i = 0; i < dpad_entry->dpad_count; i++) { const char *dpad_path_string; size_t dpad_path_length; oxr_path_get_string(log, inst, dpad_entry->dpads[i].binding, &dpad_path_string, &dpad_path_length); if (strncmp(bound_path_string, dpad_path_string, dpad_path_length) == 0) { *out_dpad_binding = &dpad_entry->dpads[i]; return true; } } } return false; } /*! * Try to produce a transform chain to create a dpad button from the selected input * (potentially using other inputs like `/force` in the process) * * Populates @p action_input->transforms and @p action_input->transform_count on * success. * * @returns false if it could not, true if it could */ static bool oxr_action_populate_input_transform_dpad(struct oxr_logger *log, struct oxr_sink_logger *slog, struct oxr_session *sess, struct oxr_action *act, struct oxr_dpad_entry *dpad_entry, enum oxr_dpad_region dpad_region, struct oxr_interaction_profile *profile, struct oxr_action_input *action_inputs, uint32_t action_input_count, uint32_t selected_input) { struct oxr_action_input *action_input = &(action_inputs[selected_input]); assert(action_input->transforms == NULL); assert(action_input->transform_count == 0); const char *bound_path_string; size_t bound_path_length; oxr_path_get_string(log, sess->sys->inst, action_input->bound_path, &bound_path_string, &bound_path_length); // find correct dpad entry struct oxr_dpad_binding_modification *dpad_binding_modification = NULL; find_matching_dpad(log, sess->sys->inst, dpad_entry, bound_path_string, &dpad_binding_modification); enum xrt_input_type t = XRT_GET_INPUT_TYPE(action_input->input->name); enum xrt_input_type activate_t = XRT_GET_INPUT_TYPE(action_input->dpad_activate_name); return oxr_input_transform_create_chain_dpad( log, slog, t, act->data->action_type, bound_path_string, dpad_binding_modification, dpad_region, activate_t, action_input->dpad_activate, &action_input->transforms, &action_input->transform_count); } // based on get_subaction_path_from_path static bool get_dpad_region_from_path(struct oxr_logger *log, struct oxr_instance *inst, XrPath path, enum oxr_dpad_region *out_dpad_region) { const char *str = NULL; size_t length = 0; XrResult ret; ret = oxr_path_get_string(log, inst, path, &str, &length); if (ret != XR_SUCCESS) { return false; } // TODO: surely there's a better way to do this? if (length >= 10 && strncmp("/dpad_left", str + (length - 10), 10) == 0) { *out_dpad_region = OXR_DPAD_REGION_LEFT; return true; } if (length >= 11 && strncmp("/dpad_right", str + (length - 11), 11) == 0) { *out_dpad_region = OXR_DPAD_REGION_RIGHT; return true; } if (length >= 8 && strncmp("/dpad_up", str + (length - 8), 8) == 0) { *out_dpad_region = OXR_DPAD_REGION_UP; return true; } if (length >= 10 && strncmp("/dpad_down", str + (length - 10), 10) == 0) { *out_dpad_region = OXR_DPAD_REGION_DOWN; return true; } if (length >= 12 && strncmp("/dpad_center", str + (length - 12), 12) == 0) { *out_dpad_region = OXR_DPAD_REGION_CENTER; return true; } return false; } static void oxr_action_bind_io(struct oxr_logger *log, struct oxr_sink_logger *slog, struct oxr_session *sess, struct oxr_action *act, struct oxr_action_cache *cache, struct oxr_interaction_profile *profile, enum oxr_subaction_path subaction_path) { struct oxr_action_input inputs[OXR_MAX_BINDINGS_PER_ACTION] = {0}; uint32_t input_count = 0; struct oxr_action_output outputs[OXR_MAX_BINDINGS_PER_ACTION] = {0}; uint32_t output_count = 0; get_binding(log, slog, sess, act, profile, subaction_path, inputs, &input_count, outputs, &output_count); cache->current.active = false; if (input_count > 0) { uint32_t count = 0; cache->current.active = true; cache->inputs = U_TYPED_ARRAY_CALLOC(struct oxr_action_input, input_count); for (uint32_t i = 0; i < input_count; i++) { // Only add the input if we can find a transform. enum oxr_dpad_region dpad_region; if (get_dpad_region_from_path(log, sess->sys->inst, inputs[i].bound_path, &dpad_region)) { struct oxr_dpad_entry *entry = oxr_dpad_state_get(&profile->dpad_state, act->act_set->act_set_key); if (oxr_action_populate_input_transform_dpad(log, slog, sess, act, entry, dpad_region, profile, inputs, input_count, i)) { cache->inputs[count++] = inputs[i]; continue; } } else if (oxr_action_populate_input_transform(log, slog, sess, act, &(inputs[i]))) { cache->inputs[count++] = inputs[i]; continue; } oxr_slog(slog, "\t\t\t\tRejected! (NO TRANSFORM)\n"); } // No inputs found, prented we never bound it. if (count == 0) { free(cache->inputs); cache->inputs = NULL; } cache->input_count = count; } if (output_count > 0) { cache->current.active = true; cache->outputs = U_TYPED_ARRAY_CALLOC(struct oxr_action_output, output_count); for (uint32_t i = 0; i < output_count; i++) { cache->outputs[i] = outputs[i]; } cache->output_count = output_count; } } /* * * Session functions. * */ /*! * Given an Action Set handle, return the @ref oxr_action_set and the associated * @ref oxr_action_set_attachment in the given Session. * * @private @memberof oxr_session */ static void oxr_session_get_action_set_attachment(struct oxr_session *sess, XrActionSet actionSet, struct oxr_action_set_attachment **act_set_attached, struct oxr_action_set **act_set) { void *ptr = NULL; *act_set = XRT_CAST_OXR_HANDLE_TO_PTR(struct oxr_action_set *, actionSet); *act_set_attached = NULL; // In case no action_sets have been attached. if (sess->act_sets_attachments_by_key == NULL) { return; } int ret = u_hashmap_int_find(sess->act_sets_attachments_by_key, (*act_set)->act_set_key, &ptr); if (ret == 0) { *act_set_attached = (struct oxr_action_set_attachment *)ptr; } } /*! * Given an action act_key, look up the @ref oxr_action_attachment of the * associated action in the given Session. * * @private @memberof oxr_session */ static void oxr_session_get_action_attachment(struct oxr_session *sess, uint32_t act_key, struct oxr_action_attachment **out_act_attached) { void *ptr = NULL; int ret = u_hashmap_int_find(sess->act_attachments_by_key, act_key, &ptr); if (ret == 0) { *out_act_attached = (struct oxr_action_attachment *)ptr; } } static inline size_t oxr_handle_base_get_num_children(struct oxr_handle_base *hb) { size_t ret = 0; for (uint32_t i = 0; i < XRT_MAX_HANDLE_CHILDREN; ++i) { if (hb->children[i] != NULL) { ++ret; } } return ret; } XrResult oxr_session_attach_action_sets(struct oxr_logger *log, struct oxr_session *sess, const XrSessionActionSetsAttachInfo *bindInfo) { struct oxr_instance *inst = sess->sys->inst; struct oxr_profiles_per_subaction profiles = {0}; #define FIND_PROFILE(X) oxr_find_profile_for_device(log, inst, GET_XDEV_BY_ROLE(sess->sys, X), &profiles.X); OXR_FOR_EACH_VALID_SUBACTION_PATH(FIND_PROFILE) #undef FIND_PROFILE // Allocate room for list. No need to check if anything has been // attached the API function does that. sess->action_set_attachment_count = bindInfo->countActionSets; sess->act_set_attachments = U_TYPED_ARRAY_CALLOC(struct oxr_action_set_attachment, sess->action_set_attachment_count); // Set up the per-session data for these action sets. for (uint32_t i = 0; i < sess->action_set_attachment_count; i++) { struct oxr_action_set *act_set = XRT_CAST_OXR_HANDLE_TO_PTR(struct oxr_action_set *, bindInfo->actionSets[i]); struct oxr_action_set_ref *act_set_ref = act_set->data; act_set_ref->ever_attached = true; struct oxr_action_set_attachment *act_set_attached = &sess->act_set_attachments[i]; oxr_action_set_attachment_init(log, sess, act_set, act_set_attached); // Allocate the action attachments for this set. act_set_attached->action_attachment_count = oxr_handle_base_get_num_children(&act_set->handle); act_set_attached->act_attachments = U_TYPED_ARRAY_CALLOC(struct oxr_action_attachment, act_set_attached->action_attachment_count); // Set up the per-session data for the actions. uint32_t child_index = 0; for (uint32_t k = 0; k < XRT_MAX_HANDLE_CHILDREN; k++) { struct oxr_action *act = (struct oxr_action *)act_set->handle.children[k]; if (act == NULL) { continue; } struct oxr_action_attachment *act_attached = &act_set_attached->act_attachments[child_index]; oxr_action_attachment_init(log, act_set_attached, act_attached, act); oxr_action_attachment_bind(log, act_attached, act, &profiles); ++child_index; } } #define POPULATE_PROFILE(X) \ if (profiles.X != NULL) { \ sess->X = profiles.X->path; \ oxr_event_push_XrEventDataInteractionProfileChanged(log, sess); \ } OXR_FOR_EACH_VALID_SUBACTION_PATH(POPULATE_PROFILE) #undef POPULATE_PROFILE return oxr_session_success_result(sess); } XrResult oxr_action_sync_data(struct oxr_logger *log, struct oxr_session *sess, uint32_t countActionSets, const XrActiveActionSet *actionSets) { struct oxr_action_set *act_set = NULL; struct oxr_action_set_attachment *act_set_attached = NULL; // Check that all action sets has been attached. for (uint32_t i = 0; i < countActionSets; i++) { oxr_session_get_action_set_attachment(sess, actionSets[i].actionSet, &act_set_attached, &act_set); if (act_set_attached == NULL) { return oxr_error(log, XR_ERROR_ACTIONSET_NOT_ATTACHED, "(actionSets[%i].actionSet) action set '%s' has " "not been attached to this session", i, act_set != NULL ? act_set->data->name : "NULL"); } } // Synchronize outputs to this time. int64_t now = time_state_get_now(sess->sys->inst->timekeeping); // Loop over all xdev devices. for (size_t i = 0; i < sess->sys->xsysd->xdev_count; i++) { oxr_xdev_update(sess->sys->xsysd->xdevs[i]); } // Reset all action set attachments. for (size_t i = 0; i < sess->action_set_attachment_count; ++i) { act_set_attached = &sess->act_set_attachments[i]; U_ZERO(&act_set_attached->requested_subaction_paths); } // Go over all requested action sets and update their attachment. //! @todo can be listed more than once with different paths! for (uint32_t i = 0; i < countActionSets; i++) { struct oxr_subaction_paths subaction_paths; oxr_session_get_action_set_attachment(sess, actionSets[i].actionSet, &act_set_attached, &act_set); assert(act_set_attached != NULL); if (!oxr_classify_sub_action_paths(log, sess->sys->inst, 1, &actionSets[i].subactionPath, &subaction_paths)) { return XR_ERROR_PATH_UNSUPPORTED; } act_set_attached->requested_subaction_paths.any |= subaction_paths.any; /* never error when requesting any subactionpath */ bool any_action_with_subactionpath = subaction_paths.any; #define ACCUMULATE_REQUESTED(X) act_set_attached->requested_subaction_paths.X |= subaction_paths.X; OXR_FOR_EACH_SUBACTION_PATH(ACCUMULATE_REQUESTED) #undef ACCUMULATE_REQUESTED /* check if we have at least one action for requested subactionpath */ for (uint32_t k = 0; k < act_set_attached->action_attachment_count; k++) { struct oxr_action_attachment *act_attached = &act_set_attached->act_attachments[k]; if (act_attached == NULL) { continue; } #define ACCUMULATE_REQUESTED(X) \ any_action_with_subactionpath |= subaction_paths.X && act_attached->act_ref->subaction_paths.X; OXR_FOR_EACH_SUBACTION_PATH(ACCUMULATE_REQUESTED) #undef ACCUMULATE_REQUESTED } if (!any_action_with_subactionpath) { return oxr_error(log, XR_ERROR_PATH_UNSUPPORTED, "No action with specified subactionpath in actionset"); } } // Now, update all action attachments for (size_t i = 0; i < sess->action_set_attachment_count; ++i) { act_set_attached = &sess->act_set_attachments[i]; struct oxr_subaction_paths subaction_paths = act_set_attached->requested_subaction_paths; for (uint32_t k = 0; k < act_set_attached->action_attachment_count; k++) { struct oxr_action_attachment *act_attached = &act_set_attached->act_attachments[k]; if (act_attached == NULL) { continue; } oxr_action_attachment_update(log, sess, countActionSets, actionSets, act_attached, now, subaction_paths); } } return oxr_session_success_focused_result(sess); } static void add_path_to_set(XrPath path_set[OXR_MAX_BINDINGS_PER_ACTION], XrPath new_path, uint32_t *inout_path_count) { const uint32_t n = *inout_path_count; // Shouldn't be full assert(n < OXR_MAX_BINDINGS_PER_ACTION); for (uint32_t i = 0; i < n; ++i) { if (new_path == path_set[i]) { return; } // Should have no gaps assert(path_set[i] != 0); } path_set[n] = new_path; (*inout_path_count)++; } XrResult oxr_action_enumerate_bound_sources(struct oxr_logger *log, struct oxr_session *sess, uint32_t act_key, uint32_t sourceCapacityInput, uint32_t *sourceCountOutput, XrPath *sources) { struct oxr_action_attachment *act_attached = NULL; uint32_t path_count = 0; XrPath temp[OXR_MAX_BINDINGS_PER_ACTION] = {0}; oxr_session_get_action_attachment(sess, act_key, &act_attached); if (act_attached == NULL) { return oxr_error(log, XR_ERROR_RUNTIME_FAILURE, "act_key did not find any action"); } #define ACCUMULATE_PATHS(X) \ if (act_attached->X.input_count > 0) { \ for (uint32_t i = 0; i < act_attached->X.input_count; i++) { \ add_path_to_set(temp, act_attached->X.inputs[i].bound_path, &path_count); \ } \ } \ if (act_attached->X.output_count > 0) { \ for (uint32_t i = 0; i < act_attached->X.output_count; i++) { \ add_path_to_set(temp, act_attached->X.outputs[i].bound_path, &path_count); \ } \ } OXR_FOR_EACH_SUBACTION_PATH(ACCUMULATE_PATHS) #undef ACCUMULATE_PATHS OXR_TWO_CALL_HELPER(log, sourceCapacityInput, sourceCountOutput, sources, path_count, temp, oxr_session_success_result(sess)); } /* * * Action get functions. * */ #define OXR_ACTION_GET_XR_STATE_FROM_ACTION_STATE_COMMON(ACTION_STATE, DATA) \ do { \ DATA->lastChangeTime = time_state_monotonic_to_ts_ns(inst->timekeeping, ACTION_STATE->timestamp); \ DATA->changedSinceLastSync = ACTION_STATE->changed; \ DATA->isActive = XR_TRUE; \ } while (0) static void get_xr_state_from_action_state_bool(struct oxr_instance *inst, struct oxr_action_state *state, XrActionStateBoolean *data) { /* only get here if the action is active! */ assert(state->active); OXR_ACTION_GET_XR_STATE_FROM_ACTION_STATE_COMMON(state, data); data->currentState = state->value.boolean; } static void get_xr_state_from_action_state_vec1(struct oxr_instance *inst, struct oxr_action_state *state, XrActionStateFloat *data) { /* only get here if the action is active! */ assert(state->active); OXR_ACTION_GET_XR_STATE_FROM_ACTION_STATE_COMMON(state, data); data->currentState = state->value.vec1.x; } static void get_xr_state_from_action_state_vec2(struct oxr_instance *inst, struct oxr_action_state *state, XrActionStateVector2f *data) { /* only get here if the action is active! */ assert(state->active); OXR_ACTION_GET_XR_STATE_FROM_ACTION_STATE_COMMON(state, data); data->currentState.x = state->value.vec2.x; data->currentState.y = state->value.vec2.y; } /*! * This populates the internals of action get state functions. * * @note Keep this synchronized with OXR_FOR_EACH_SUBACTION_PATH! */ #define OXR_ACTION_GET_FILLER(TYPE) \ if (subaction_paths.any && act_attached->any_state.active) { \ get_xr_state_from_action_state_##TYPE(sess->sys->inst, &act_attached->any_state, data); \ } \ if (subaction_paths.user && act_attached->user.current.active) { \ get_xr_state_from_action_state_##TYPE(sess->sys->inst, &act_attached->user.current, data); \ } \ if (subaction_paths.head && act_attached->head.current.active) { \ get_xr_state_from_action_state_##TYPE(sess->sys->inst, &act_attached->head.current, data); \ } \ if (subaction_paths.left && act_attached->left.current.active) { \ get_xr_state_from_action_state_##TYPE(sess->sys->inst, &act_attached->left.current, data); \ } \ if (subaction_paths.right && act_attached->right.current.active) { \ get_xr_state_from_action_state_##TYPE(sess->sys->inst, &act_attached->right.current, data); \ } \ if (subaction_paths.gamepad && act_attached->gamepad.current.active) { \ get_xr_state_from_action_state_##TYPE(sess->sys->inst, &act_attached->gamepad.current, data); \ } /*! * Clear the actual data members of the XrActionState* types, to have the * correct return value in case of the action being not active */ #define OXR_ACTION_RESET_XR_ACTION_STATE(data) \ do { \ data->isActive = XR_FALSE; \ data->changedSinceLastSync = XR_FALSE; \ data->lastChangeTime = 0; \ U_ZERO(&data->currentState); \ } while (0) XrResult oxr_action_get_boolean(struct oxr_logger *log, struct oxr_session *sess, uint32_t act_key, struct oxr_subaction_paths subaction_paths, XrActionStateBoolean *data) { struct oxr_action_attachment *act_attached = NULL; oxr_session_get_action_attachment(sess, act_key, &act_attached); if (act_attached == NULL) { return oxr_error(log, XR_ERROR_ACTIONSET_NOT_ATTACHED, "Action has not been attached to this session"); } OXR_ACTION_RESET_XR_ACTION_STATE(data); OXR_ACTION_GET_FILLER(bool); return oxr_session_success_result(sess); } XrResult oxr_action_get_vector1f(struct oxr_logger *log, struct oxr_session *sess, uint32_t act_key, struct oxr_subaction_paths subaction_paths, XrActionStateFloat *data) { struct oxr_action_attachment *act_attached = NULL; oxr_session_get_action_attachment(sess, act_key, &act_attached); if (act_attached == NULL) { return oxr_error(log, XR_ERROR_ACTIONSET_NOT_ATTACHED, "Action has not been attached to this session"); } OXR_ACTION_RESET_XR_ACTION_STATE(data); OXR_ACTION_GET_FILLER(vec1); return oxr_session_success_result(sess); } XrResult oxr_action_get_vector2f(struct oxr_logger *log, struct oxr_session *sess, uint32_t act_key, struct oxr_subaction_paths subaction_paths, XrActionStateVector2f *data) { struct oxr_action_attachment *act_attached = NULL; oxr_session_get_action_attachment(sess, act_key, &act_attached); if (act_attached == NULL) { return oxr_error(log, XR_ERROR_ACTIONSET_NOT_ATTACHED, "Action has not been attached to this session"); } OXR_ACTION_RESET_XR_ACTION_STATE(data); OXR_ACTION_GET_FILLER(vec2); return oxr_session_success_result(sess); } XrResult oxr_action_get_pose(struct oxr_logger *log, struct oxr_session *sess, uint32_t act_key, struct oxr_subaction_paths subaction_paths, XrActionStatePose *data) { struct oxr_action_attachment *act_attached = NULL; oxr_session_get_action_attachment(sess, act_key, &act_attached); if (act_attached == NULL) { return oxr_error(log, XR_ERROR_ACTIONSET_NOT_ATTACHED, "Action has not been attached to this session"); } // For poses on the any path we select a single path. if (subaction_paths.any) { subaction_paths = act_attached->any_pose_subaction_path; } data->isActive = XR_FALSE; /* * The sub path any is used as a catch all here to see if any */ #define COMPUTE_ACTIVE(X) \ if (subaction_paths.X) { \ data->isActive |= act_attached->X.current.active; \ } OXR_FOR_EACH_VALID_SUBACTION_PATH(COMPUTE_ACTIVE) #undef COMPUTE_ACTIVE return oxr_session_success_result(sess); } /* * * Haptic feedback functions. * */ static void set_action_output_vibration(struct oxr_session *sess, struct oxr_action_cache *cache, int64_t stop, const XrHapticVibration *data) { cache->stop_output_time = stop; union xrt_output_value value = {0}; value.vibration.frequency = data->frequency; value.vibration.amplitude = data->amplitude; value.vibration.duration_ns = data->duration; for (uint32_t i = 0; i < cache->output_count; i++) { struct oxr_action_output *output = &cache->outputs[i]; struct xrt_device *xdev = output->xdev; xrt_device_set_output(xdev, output->name, &value); } } XrResult oxr_action_apply_haptic_feedback(struct oxr_logger *log, struct oxr_session *sess, uint32_t act_key, struct oxr_subaction_paths subaction_paths, const XrHapticBaseHeader *hapticEvent) { struct oxr_action_attachment *act_attached = NULL; oxr_session_get_action_attachment(sess, act_key, &act_attached); if (act_attached == NULL) { return oxr_error(log, XR_ERROR_ACTIONSET_NOT_ATTACHED, "Action has not been attached to this session"); } const XrHapticVibration *data = (const XrHapticVibration *)hapticEvent; // This should all be moved into the drivers. const int64_t min_pulse_time_ns = time_s_to_ns(0.1); int64_t now_ns = time_state_get_now(sess->sys->inst->timekeeping); int64_t stop_ns = 0; if (data->duration <= 0) { stop_ns = now_ns + min_pulse_time_ns; } else { stop_ns = now_ns + data->duration; } #define SET_OUT_VIBRATION(X) \ if (act_attached->X.current.active && (subaction_paths.X || subaction_paths.any)) { \ set_action_output_vibration(sess, &act_attached->X, stop_ns, data); \ } OXR_FOR_EACH_SUBACTION_PATH(SET_OUT_VIBRATION) #undef SET_OUT_VIBRATION return oxr_session_success_result(sess); } XrResult oxr_action_stop_haptic_feedback(struct oxr_logger *log, struct oxr_session *sess, uint32_t act_key, struct oxr_subaction_paths subaction_paths) { struct oxr_action_attachment *act_attached = NULL; oxr_session_get_action_attachment(sess, act_key, &act_attached); if (act_attached == NULL) { return oxr_error(log, XR_ERROR_ACTIONSET_NOT_ATTACHED, "Action has not been attached to this session"); } #define STOP_VIBRATION(X) \ if (act_attached->X.current.active && (subaction_paths.X || subaction_paths.any)) { \ oxr_action_cache_stop_output(log, sess, &act_attached->X); \ } OXR_FOR_EACH_SUBACTION_PATH(STOP_VIBRATION) #undef STOP_VIBRATION return oxr_session_success_result(sess); }