/** * @brief Example of using a shared memory to pass a mostly fixed set of telemetry * data to another process. * * Note that use of the shared memory reduces precision of the output and * increases latency between event and possible reaction. */ // Windows stuff. #define WINVER 0x0500 #define _WIN32_WINNT 0x0500 #include #include #include #include #include // SDK #include "scssdk_telemetry.h" #include "eurotrucks2/scssdk_eut2.h" #include "eurotrucks2/scssdk_telemetry_eut2.h" #include "amtrucks/scssdk_ats.h" #include "amtrucks/scssdk_telemetry_ats.h" #define UNUSED(x) /** * @name Callbacks remembered from the initialization info. */ //@{ scs_telemetry_register_for_channel_t register_for_channel = NULL; scs_telemetry_unregister_from_channel_t unregister_from_channel = NULL; scs_log_t game_log = NULL; //@} /** * @brief Prints message to game log. */ void log_line(const scs_log_type_t type, const char *const text, ...) { if (! game_log) { return; } char formated[1000]; va_list args; va_start(args, text); vsnprintf_s(formated, sizeof(formated), _TRUNCATE, text, args); formated[sizeof(formated) - 1] = 0; va_end(args); game_log(type, formated); } const size_t MAX_SUPPORTED_WHEEL_COUNT = 8; #pragma pack(push) #pragma pack(1) /** * @brief The layout of the shared memory. */ struct telemetry_state_t { scs_u8_t running; // Is the telemetry running or it is paused? scs_value_dplacement_t ws_truck_placement; // SCS_TELEMETRY_TRUCK_CHANNEL_world_placement scs_float_t speedometer_speed; // SCS_TELEMETRY_TRUCK_CHANNEL_speed scs_float_t rpm; // SCS_TELEMETRY_TRUCK_CHANNEL_engine_rpm scs_s32_t gear; // SCS_TELEMETRY_TRUCK_CHANNEL_engine_gear scs_float_t steering; // SCS_TELEMETRY_TRUCK_CHANNEL_effective_steering scs_float_t throttle; // SCS_TELEMETRY_TRUCK_CHANNEL_effective_throttle scs_float_t brake; // SCS_TELEMETRY_TRUCK_CHANNEL_effective_brake scs_float_t clutch; // SCS_TELEMETRY_TRUCK_CHANNEL_effective_clutch scs_value_fvector_t linear_valocity; // SCS_TELEMETRY_TRUCK_CHANNEL_local_linear_velocity scs_value_fvector_t angular_velocity; // SCS_TELEMETRY_TRUCK_CHANNEL_local_angular_velocity scs_value_fvector_t linear_acceleration; // SCS_TELEMETRY_TRUCK_CHANNEL_local_linear_acceleration scs_value_fvector_t angular_acceleration; // SCS_TELEMETRY_TRUCK_CHANNEL_local_angular_acceleration scs_value_fvector_t cabin_angular_velocity; // SCS_TELEMETRY_TRUCK_CHANNEL_cabin_angular_velocity scs_value_fvector_t cabin_angular_acceleration; // SCS_TELEMETRY_TRUCK_CHANNEL_cabin_angular_acceleration scs_u32_t wheel_count; // SCS_TELEMETRY_CONFIG_ATTRIBUTE_wheel_count scs_float_t wheel_deflections[MAX_SUPPORTED_WHEEL_COUNT]; // SCS_TELEMETRY_TRUCK_CHANNEL_wheel_susp_deflection }; #pragma pack(pop) /** * @brief Handle of the memory mapping. */ HANDLE memory_mapping = NULL; /** * @brief Block inside the shared memory. */ telemetry_state_t *shared_memory = NULL; /** * @brief Deinitialize the shared memory objects. */ void deinitialize_shared_memory(void) { if (shared_memory) { UnmapViewOfFile(shared_memory); shared_memory = NULL; } if (memory_mapping) { CloseHandle(memory_mapping); memory_mapping = NULL; } } /** * @brief Initialize the shared memory objects. */ bool initialize_shared_memory(void) { // Setup the mapping. const DWORD memory_size = sizeof(telemetry_state_t); memory_mapping = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT, 0, memory_size, "SCSTelemetryExample"); if (! memory_mapping) { log_line(SCS_LOG_TYPE_error, "Unable to create shared memory %08X", GetLastError()); deinitialize_shared_memory(); return false; } if (GetLastError() == ERROR_ALREADY_EXISTS) { log_line(SCS_LOG_TYPE_error, "Shared memory is already in use."); deinitialize_shared_memory(); return false; } shared_memory = static_cast(MapViewOfFile(memory_mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0)); if (! shared_memory) { log_line(SCS_LOG_TYPE_error, "Unable to map the view %08X", GetLastError()); deinitialize_shared_memory(); return false; } // Defaults in the structure. memset(shared_memory, 0, memory_size); // We are always initialized in the paused state. shared_memory->running = 0; // No wheels until we get corresponding configuration message. shared_memory->wheel_count = 0; return true; } /** * @brief Float storage callback. * * Can be used together with SCS_TELEMETRY_CHANNEL_FLAG_no_value in which case it * will store zero if the value is not available. */ SCSAPI_VOID telemetry_store_float(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context) { assert(context); scs_float_t *const storage = static_cast(context); if (value) { assert(value->type == SCS_VALUE_TYPE_float); *storage = value->value_float.value; } else { *storage = 0.0f; } } /** * @brief s32 storage callback. * * Can be used together with SCS_TELEMETRY_CHANNEL_FLAG_no_value in which case it * will store zero if the value is not available. */ SCSAPI_VOID telemetry_store_s32(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context) { assert(context); scs_s32_t *const storage = static_cast(context); if (value) { assert(value->type == SCS_VALUE_TYPE_s32); *storage = value->value_s32.value; } else { *storage = 0; } } /** * @brief Orientation storage callback. * * Can be used together with SCS_TELEMETRY_CHANNEL_FLAG_no_value in which case it * will store zero if the value is not available. */ SCSAPI_VOID telemetry_store_orientation(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context) { assert(context); scs_value_euler_t *const storage = static_cast(context); if (value) { assert(value->type == SCS_VALUE_TYPE_euler); *storage = value->value_euler; } else { storage->heading = 0.0f; storage->pitch = 0.0f; storage->roll = 0.0f; } } /** * @brief Vector storage callback. * * Can be used together with SCS_TELEMETRY_CHANNEL_FLAG_no_value in which case it * will store zero if the value is not available. */ SCSAPI_VOID telemetry_store_fvector(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context) { assert(context); scs_value_fvector_t *const storage = static_cast(context); if (value) { assert(value->type == SCS_VALUE_TYPE_fvector); *storage = value->value_fvector; } else { storage->x = 0.0f; storage->y = 0.0f; storage->z = 0.0f; } } /** * @brief Placement storage callback. * * Can be used together with SCS_TELEMETRY_CHANNEL_FLAG_no_value in which case it * will store zeros if the value is not available. */ SCSAPI_VOID telemetry_store_dplacement(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context) { assert(context); scs_value_dplacement_t *const storage = static_cast(context); if (value) { assert(value->type == SCS_VALUE_TYPE_dplacement); *storage = value->value_dplacement; } else { storage->position.x = 0.0; storage->position.y = 0.0; storage->position.z = 0.0; storage->orientation.heading = 0.0f; storage->orientation.pitch = 0.0f; storage->orientation.roll = 0.0f; } } /** * @brief Finds attribute with specified name in the configuration structure. * * Returns NULL if the attribute was not found or if it is not of the expected type. */ const scs_named_value_t *find_attribute(const scs_telemetry_configuration_t &configuration, const char *const name, const scs_u32_t index, const scs_value_type_t expected_type) { for (const scs_named_value_t *current = configuration.attributes; current->name; ++current) { if ((current->index != index) || (strcmp(current->name, name) != 0)) { continue; } if (current->value.type == expected_type) { return current; } log_line(SCS_LOG_TYPE_error, "Attribute %s has unexpected type %u", name, static_cast(current->value.type)); break; } return NULL; } /** * @brief Called whenever the game pauses or unpauses its telemetry output. */ SCSAPI_VOID telemetry_pause(const scs_event_t event, const void *const UNUSED(event_info), const scs_context_t UNUSED(context)) { shared_memory->running = (event == SCS_TELEMETRY_EVENT_started) ? 1 : 0; } /** * @brief Called whenever configuration changes. */ SCSAPI_VOID telemetry_configuration(const scs_event_t event, const void *const event_info, const scs_context_t UNUSED(context)) { // We currently only care for the truck telemetry info. const struct scs_telemetry_configuration_t *const info = static_cast(event_info); if (strcmp(info->id, SCS_TELEMETRY_CONFIG_truck) != 0) { return; } // Determine number of wheels up to the number supported by our memory structure. const scs_named_value_t *const wheel_count_attr = find_attribute(*info, SCS_TELEMETRY_CONFIG_ATTRIBUTE_wheel_count, SCS_U32_NIL, SCS_VALUE_TYPE_u32); size_t wheel_count = wheel_count_attr ? wheel_count_attr->value.value_u32.value : 0; if (wheel_count > MAX_SUPPORTED_WHEEL_COUNT) { wheel_count = MAX_SUPPORTED_WHEEL_COUNT; } // Update the wheel-related channel registrations to match. while (shared_memory->wheel_count > wheel_count) { --shared_memory->wheel_count; shared_memory->wheel_deflections[shared_memory->wheel_count] = 0.0f; unregister_from_channel(SCS_TELEMETRY_TRUCK_CHANNEL_wheel_susp_deflection, shared_memory->wheel_count, SCS_VALUE_TYPE_float); } while (shared_memory->wheel_count < wheel_count) { register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_wheel_susp_deflection, shared_memory->wheel_count, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, (shared_memory->wheel_deflections + shared_memory->wheel_count)); ++shared_memory->wheel_count; } } /** * @brief Telemetry API initialization function. * * See scssdk_telemetry.h */ SCSAPI_RESULT scs_telemetry_init(const scs_u32_t version, const scs_telemetry_init_params_t *const params) { // We currently support only one version of the API. if (version != SCS_TELEMETRY_VERSION_1_00) { return SCS_RESULT_unsupported; } const scs_telemetry_init_params_v100_t *const version_params = static_cast(params); game_log = version_params->common.log; // Check application version. log_line(SCS_LOG_TYPE_message, "Game '%s' %u.%u", version_params->common.game_id, SCS_GET_MAJOR_VERSION(version_params->common.game_version), SCS_GET_MINOR_VERSION(version_params->common.game_version)); if (strcmp(version_params->common.game_id, SCS_GAME_ID_EUT2) == 0) { // Below the minimum version there might be some missing features (only minor change) or // incompatible values (major change). if (version_params->common.game_version < SCS_TELEMETRY_EUT2_GAME_VERSION_1_03) { // Fixed the wheels.count attribute log_line(SCS_LOG_TYPE_error, "Too old version of the game"); game_log = NULL; return SCS_RESULT_unsupported; } if (version_params->common.game_version < SCS_TELEMETRY_EUT2_GAME_VERSION_1_07) { // Fixed the angular acceleration calculation log_line(SCS_LOG_TYPE_warning, "This version of the game has less precise output of angular acceleration of the cabin"); } // Future versions are fine as long the major version is not changed. const scs_u32_t IMPLEMENTED_VERSION = SCS_TELEMETRY_EUT2_GAME_VERSION_CURRENT; if (SCS_GET_MAJOR_VERSION(version_params->common.game_version) > SCS_GET_MAJOR_VERSION(IMPLEMENTED_VERSION)) { log_line(SCS_LOG_TYPE_warning, "Too new major version of the game, some features might behave incorrectly"); } } else if (strcmp(version_params->common.game_id, SCS_GAME_ID_ATS) == 0) { // Below the minimum version there might be some missing features (only minor change) or // incompatible values (major change). const scs_u32_t MINIMAL_VERSION = SCS_TELEMETRY_ATS_GAME_VERSION_1_00; if (version_params->common.game_version < MINIMAL_VERSION) { log_line(SCS_LOG_TYPE_warning, "WARNING: Too old version of the game, some features might behave incorrectly"); } // Future versions are fine as long the major version is not changed. const scs_u32_t IMPLEMENTED_VERSION = SCS_TELEMETRY_ATS_GAME_VERSION_CURRENT; if (SCS_GET_MAJOR_VERSION(version_params->common.game_version) > SCS_GET_MAJOR_VERSION(IMPLEMENTED_VERSION)) { log_line(SCS_LOG_TYPE_warning, "WARNING: Too new major version of the game, some features might behave incorrectly"); } } else { log_line(SCS_LOG_TYPE_warning, "Unsupported game, some features or values might behave incorrectly"); } // Register for events. Note that failure to register those basic events // likely indicates invalid usage of the api or some critical problem. As the // example requires all of them, we can not continue if the registration fails. const bool events_registered = (version_params->register_for_event(SCS_TELEMETRY_EVENT_paused, telemetry_pause, NULL) == SCS_RESULT_ok) && (version_params->register_for_event(SCS_TELEMETRY_EVENT_started, telemetry_pause, NULL) == SCS_RESULT_ok) && (version_params->register_for_event(SCS_TELEMETRY_EVENT_configuration, telemetry_configuration, NULL) == SCS_RESULT_ok) ; if (! events_registered) { // Registrations created by unsuccessful initialization are // cleared automatically so we can simply exit. log_line(SCS_LOG_TYPE_error, "Unable to register event callbacks"); game_log = NULL; return SCS_RESULT_generic_error; } // Initialize the shared memory. if (! initialize_shared_memory()) { log_line(SCS_LOG_TYPE_error, "Unable to initialize shared memory"); game_log = NULL; return SCS_RESULT_generic_error; } // Register all changes we are interested in. Note that some wheel-related channels will be initialized when we // receive a configuration event. The channel might be missing if the game does not support it (SCS_RESULT_not_found) // or if does not support the requested type (SCS_RESULT_unsupported_type). For purpose of this example we ignore // the failures so the unsupported channels will remain at theirs default value. #define register_channel(name, index, type, field) version_params->register_for_channel(SCS_TELEMETRY_##name, index, SCS_VALUE_TYPE_##type, SCS_TELEMETRY_CHANNEL_FLAG_no_value, telemetry_store_##type, &shared_memory->field); register_channel(TRUCK_CHANNEL_world_placement, SCS_U32_NIL, dplacement, ws_truck_placement); register_channel(TRUCK_CHANNEL_speed, SCS_U32_NIL, float, speedometer_speed); register_channel(TRUCK_CHANNEL_engine_rpm, SCS_U32_NIL, float, rpm); register_channel(TRUCK_CHANNEL_engine_gear, SCS_U32_NIL, s32, gear); register_channel(TRUCK_CHANNEL_effective_steering, SCS_U32_NIL, float, steering); register_channel(TRUCK_CHANNEL_effective_throttle, SCS_U32_NIL, float, throttle); register_channel(TRUCK_CHANNEL_effective_brake, SCS_U32_NIL, float, brake); register_channel(TRUCK_CHANNEL_effective_clutch, SCS_U32_NIL, float, clutch); register_channel(TRUCK_CHANNEL_local_linear_velocity, SCS_U32_NIL, fvector, linear_valocity); register_channel(TRUCK_CHANNEL_local_angular_velocity, SCS_U32_NIL, fvector, angular_velocity); register_channel(TRUCK_CHANNEL_local_linear_acceleration, SCS_U32_NIL, fvector, linear_acceleration); register_channel(TRUCK_CHANNEL_local_angular_acceleration, SCS_U32_NIL, fvector, angular_acceleration); register_channel(TRUCK_CHANNEL_cabin_angular_velocity, SCS_U32_NIL, fvector, cabin_angular_velocity); register_channel(TRUCK_CHANNEL_cabin_angular_acceleration, SCS_U32_NIL, fvector, cabin_angular_acceleration); #undef register_channel // Remember other the functions we will use in the future. register_for_channel = version_params->register_for_channel; unregister_from_channel = version_params->unregister_from_channel; // We are done. log_line(SCS_LOG_TYPE_message, "Memory telemetry example initialized"); return SCS_RESULT_ok; } /** * @brief Telemetry API deinitialization function. * * See scssdk_telemetry.h */ SCSAPI_VOID scs_telemetry_shutdown(void) { // Any cleanup needed. The registrations will be removed automatically // so there is no need to do that manually. deinitialize_shared_memory(); unregister_from_channel = NULL; register_for_channel = NULL; game_log = NULL; } // Cleanup BOOL APIENTRY DllMain( HMODULE module, DWORD reason_for_call, LPVOID reseved ) { return TRUE; } // EOF //