static bool menu_input_key_bind_set_mode( enum menu_input_ctl_state state, void *data) { unsigned index_offset; input_keyboard_ctx_wait_t keys; menu_handle_t *menu = NULL; menu_input_t *menu_input = menu_input_get_ptr(); rarch_setting_t *setting = (rarch_setting_t*)data; settings_t *settings = config_get_ptr(); if (!setting) return false; if (!menu_driver_ctl(RARCH_MENU_CTL_DRIVER_DATA_GET, &menu)) return false; if (menu_input_key_bind_set_mode_common(state, setting) == -1) return false; index_offset = menu_setting_get_index_offset(setting); bind_port = settings->input.joypad_map[index_offset]; menu_input_key_bind_poll_bind_get_rested_axes( &menu_input->binds, bind_port); menu_input_key_bind_poll_bind_state( &menu_input->binds, bind_port, false); menu_input->binds.timeout_end = cpu_features_get_time_usec() + MENU_KEYBOARD_BIND_TIMEOUT_SECONDS * 1000000; keys.userdata = menu; keys.cb = menu_input_key_bind_custom_bind_keyboard_cb; input_keyboard_ctl(RARCH_INPUT_KEYBOARD_CTL_START_WAIT_KEYS, &keys); return true; }
long GetTicks(void) { // in MSec #ifndef _ANDROID_ #ifdef __CELLOS_LV2__ //#warning "GetTick PS3\n" unsigned long ticks_micro; uint64_t secs; uint64_t nsecs; sys_time_get_current_time(&secs, &nsecs); ticks_micro = secs * 1000000UL + (nsecs / 1000); return ticks_micro;///1000; #elif defined(WIIU) return (cpu_features_get_time_usec());///1000; #else struct timeval tv; gettimeofday (&tv, NULL); return (tv.tv_sec*1000000 + tv.tv_usec);///1000; #endif #else struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return (now.tv_sec*1000000 + now.tv_nsec/1000);///1000; #endif }
static bool menu_input_key_bind_custom_bind_keyboard_cb( void *data, unsigned code) { settings_t *settings = config_get_ptr(); menu_input_binds.target->key = (enum retro_key)code; menu_input_binds.begin++; menu_input_binds.target++; menu_input_binds.timeout_end = cpu_features_get_time_usec() + settings->input.bind_timeout * 1000000; return (menu_input_binds.begin <= menu_input_binds.last); }
static bool menu_input_key_bind_custom_bind_keyboard_cb( void *data, unsigned code) { menu_input_t *menu_input = menu_input_get_ptr(); if (!menu_input) return false; menu_input->binds.target->key = (enum retro_key)code; menu_input->binds.begin++; menu_input->binds.target++; menu_input->binds.timeout_end = cpu_features_get_time_usec() + MENU_KEYBOARD_BIND_TIMEOUT_SECONDS * 1000000; return (menu_input->binds.begin <= menu_input->binds.last); }
void menu_animation_update_time(bool timedate_enable) { static retro_time_t last_clock_update = 0; cur_time = cpu_features_get_time_usec(); delta_time = cur_time - old_time; if (delta_time >= IDEAL_DELTA_TIME* 4) delta_time = IDEAL_DELTA_TIME * 4; if (delta_time <= IDEAL_DELTA_TIME / 4) delta_time = IDEAL_DELTA_TIME / 4; old_time = cur_time; if (((cur_time - last_clock_update) > 1000000) && timedate_enable) { animation_is_active = true; last_clock_update = cur_time; } }
static bool video_thread_init(thread_video_t *thr, const video_info_t *info, const input_driver_t **input, void **input_data) { size_t max_size; thread_packet_t pkt = {CMD_INIT}; thr->lock = slock_new(); thr->alpha_lock = slock_new(); thr->frame.lock = slock_new(); thr->cond_cmd = scond_new(); thr->cond_thread = scond_new(); thr->input = input; thr->input_data = input_data; thr->info = *info; thr->alive = true; thr->focus = true; thr->has_windowed = true; thr->suppress_screensaver = true; max_size = info->input_scale * RARCH_SCALE_BASE; max_size *= max_size; max_size *= info->rgb32 ? sizeof(uint32_t) : sizeof(uint16_t); thr->frame.buffer = (uint8_t*)malloc(max_size); if (!thr->frame.buffer) return false; memset(thr->frame.buffer, 0x80, max_size); thr->last_time = cpu_features_get_time_usec(); thr->thread = sthread_create(video_thread_loop, thr); if (!thr->thread) return false; video_thread_send_and_wait_user_to_thread(thr, &pkt); thr->send_and_wait = video_thread_send_and_wait_user_to_thread; return pkt.data.b; }
/** * runloop_iterate: * * Run Libretro core in RetroArch for one frame. * * Returns: 0 on success, 1 if we have to wait until * button input in order to wake up the loop, * -1 if we forcibly quit out of the RetroArch iteration loop. **/ int runloop_iterate(unsigned *sleep_ms) { unsigned i; event_cmd_state_t cmd; retro_time_t current, target, to_sleep_ms; static retro_input_t last_input = {0}; event_cmd_state_t *cmd_ptr = &cmd; static retro_time_t frame_limit_minimum_time = 0.0; static retro_time_t frame_limit_last_time = 0.0; settings_t *settings = config_get_ptr(); cmd.state[1] = last_input; cmd.state[0] = input_keys_pressed(); last_input = cmd.state[0]; runloop_ctl(RUNLOOP_CTL_UNSET_FRAME_TIME_LAST, NULL); if (runloop_ctl(RUNLOOP_CTL_SHOULD_SET_FRAME_LIMIT, NULL)) { struct retro_system_av_info *av_info = video_viewport_get_system_av_info(); float fastforward_ratio = (settings->fastforward_ratio == 0.0f) ? 1.0f : settings->fastforward_ratio; frame_limit_last_time = cpu_features_get_time_usec(); frame_limit_minimum_time = (retro_time_t)roundf(1000000.0f / (av_info->timing.fps * fastforward_ratio)); runloop_ctl(RUNLOOP_CTL_UNSET_FRAME_LIMIT, NULL); } if (input_driver_is_flushing_input()) { input_driver_unset_flushing_input(); if (cmd.state[0].state) { cmd.state[0].state = 0; /* If core was paused before entering menu, evoke * pause toggle to wake it up. */ if (runloop_ctl(RUNLOOP_CTL_IS_PAUSED, NULL)) BIT64_SET(cmd.state[0].state, RARCH_PAUSE_TOGGLE); input_driver_set_flushing_input(); } } if (runloop_frame_time.callback) { /* Updates frame timing if frame timing callback is in use by the core. * Limits frame time if fast forward ratio throttle is enabled. */ retro_time_t current = cpu_features_get_time_usec(); retro_time_t delta = current - runloop_frame_time_last; bool is_locked_fps = (runloop_ctl(RUNLOOP_CTL_IS_PAUSED, NULL) || input_driver_is_nonblock_state()) | !!recording_driver_get_data_ptr(); if (!runloop_frame_time_last || is_locked_fps) delta = runloop_frame_time.reference; if (!is_locked_fps && runloop_ctl(RUNLOOP_CTL_IS_SLOWMOTION, NULL)) delta /= settings->slowmotion_ratio; runloop_frame_time_last = current; if (is_locked_fps) runloop_frame_time_last = 0; runloop_frame_time.callback(delta); } cmd.state[2].state = cmd.state[0].state & ~cmd.state[1].state; /* trigger */ if (runloop_cmd_triggered(cmd_ptr, RARCH_OVERLAY_NEXT)) command_event(CMD_EVENT_OVERLAY_NEXT, NULL); if (runloop_cmd_triggered(cmd_ptr, RARCH_FULLSCREEN_TOGGLE_KEY)) { bool fullscreen_toggled = !runloop_ctl(RUNLOOP_CTL_IS_PAUSED, NULL); #ifdef HAVE_MENU fullscreen_toggled = fullscreen_toggled || menu_driver_ctl(RARCH_MENU_CTL_IS_ALIVE, NULL); #endif if (fullscreen_toggled) command_event(CMD_EVENT_FULLSCREEN_TOGGLE, NULL); } if (runloop_cmd_triggered(cmd_ptr, RARCH_GRAB_MOUSE_TOGGLE)) command_event(CMD_EVENT_GRAB_MOUSE_TOGGLE, NULL); #ifdef HAVE_MENU if (runloop_cmd_menu_press(cmd_ptr) || rarch_ctl(RARCH_CTL_IS_DUMMY_CORE, NULL)) { if (menu_driver_ctl(RARCH_MENU_CTL_IS_ALIVE, NULL)) { if (rarch_ctl(RARCH_CTL_IS_INITED, NULL) && !rarch_ctl(RARCH_CTL_IS_DUMMY_CORE, NULL)) rarch_ctl(RARCH_CTL_MENU_RUNNING_FINISHED, NULL); } else rarch_ctl(RARCH_CTL_MENU_RUNNING, NULL); } #endif #ifdef HAVE_OVERLAY runloop_iterate_linefeed_overlay(settings); #endif if (runloop_iterate_time_to_exit( runloop_cmd_press(cmd_ptr, RARCH_QUIT_KEY)) != 1) { frame_limit_last_time = 0.0; return -1; } #ifdef HAVE_MENU if (menu_driver_ctl(RARCH_MENU_CTL_IS_ALIVE, NULL)) { int ret = runloop_iterate_menu((enum menu_action) menu_input_frame_retropad(cmd.state[0], cmd.state[2]), sleep_ms); if (ret == -1) goto end; return ret; } #endif if (!runloop_check_state(&cmd, &runloop_shader_dir)) { /* RetroArch has been paused. */ core_poll(); *sleep_ms = 10; return 1; } #if defined(HAVE_THREADS) autosave_lock(); #endif #ifdef HAVE_NETPLAY netplay_driver_ctl(RARCH_NETPLAY_CTL_PRE_FRAME, NULL); #endif if (bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL)) bsv_movie_ctl(BSV_MOVIE_CTL_SET_FRAME_START, NULL); camera_driver_ctl(RARCH_CAMERA_CTL_POLL, NULL); /* Update binds for analog dpad modes. */ for (i = 0; i < settings->input.max_users; i++) { if (!settings->input.analog_dpad_mode[i]) continue; input_push_analog_dpad(settings->input.binds[i], settings->input.analog_dpad_mode[i]); input_push_analog_dpad(settings->input.autoconf_binds[i], settings->input.analog_dpad_mode[i]); } if ((settings->video.frame_delay > 0) && !input_driver_is_nonblock_state()) retro_sleep(settings->video.frame_delay); core_run(); #ifdef HAVE_CHEEVOS cheevos_test(); #endif for (i = 0; i < settings->input.max_users; i++) { if (!settings->input.analog_dpad_mode[i]) continue; input_pop_analog_dpad(settings->input.binds[i]); input_pop_analog_dpad(settings->input.autoconf_binds[i]); } if (bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL)) bsv_movie_ctl(BSV_MOVIE_CTL_SET_FRAME_END, NULL); #ifdef HAVE_NETPLAY netplay_driver_ctl(RARCH_NETPLAY_CTL_POST_FRAME, NULL); #endif #if defined(HAVE_THREADS) autosave_unlock(); #endif if (!settings->fastforward_ratio) return 0; #ifdef HAVE_MENU end: #endif current = cpu_features_get_time_usec(); target = frame_limit_last_time + frame_limit_minimum_time; to_sleep_ms = (target - current) / 1000; if (to_sleep_ms > 0) { *sleep_ms = (unsigned)to_sleep_ms; /* Combat jitter a bit. */ frame_limit_last_time += frame_limit_minimum_time; return 1; } frame_limit_last_time = cpu_features_get_time_usec(); return 0; }
static bool menu_input_key_bind_iterate(char *s, size_t len) { struct menu_bind_state binds; bool timed_out = false; menu_input_t *menu_input = menu_input_get_ptr(); int64_t current = cpu_features_get_time_usec(); int timeout = (menu_input->binds.timeout_end - current) / 1000000; if (timeout <= 0) { input_driver_keyboard_mapping_set_block(false); menu_input->binds.begin++; menu_input->binds.target++; menu_input->binds.timeout_end = cpu_features_get_time_usec() + MENU_KEYBOARD_BIND_TIMEOUT_SECONDS * 1000000; timed_out = true; } snprintf(s, len, "[%s]\npress keyboard or joypad\n(timeout %d %s)", input_config_bind_map_get_desc( menu_input->binds.begin - MENU_SETTINGS_BIND_BEGIN), timeout, menu_hash_to_str(MENU_VALUE_SECONDS)); /* binds.begin is updated in keyboard_press callback. */ if (menu_input->binds.begin > menu_input->binds.last) { /* Avoid new binds triggering things right away. */ input_driver_set_flushing_input(); /* We won't be getting any key events, so just cancel early. */ if (timed_out) input_keyboard_ctl(RARCH_INPUT_KEYBOARD_CTL_CANCEL_WAIT_KEYS, NULL); return true; } binds = menu_input->binds; input_driver_keyboard_mapping_set_block(true); menu_input_key_bind_poll_bind_state(&binds, bind_port, timed_out); if ((binds.skip && !menu_input->binds.skip) || menu_input_key_bind_poll_find_trigger(&menu_input->binds, &binds)) { input_driver_keyboard_mapping_set_block(false); /* Avoid new binds triggering things right away. */ input_driver_set_flushing_input(); binds.begin++; if (binds.begin > binds.last) { input_keyboard_ctl(RARCH_INPUT_KEYBOARD_CTL_CANCEL_WAIT_KEYS, NULL); return true; } binds.target++; binds.timeout_end = cpu_features_get_time_usec() + MENU_KEYBOARD_BIND_TIMEOUT_SECONDS * 1000000; } menu_input->binds = binds; return false; }
/** * netplay_poll: * @netplay : pointer to netplay object * * Polls network to see if we have anything new. If our * network buffer is full, we simply have to block * for new input data. * * Returns: true (1) if successful, otherwise false (0). **/ static bool netplay_poll(void) { int res; if (!netplay_data->has_connection) return false; netplay_data->can_poll = false; get_self_input_state(netplay_data); /* No network side in spectate mode */ if (netplay_is_server(netplay_data) && netplay_data->spectate.enabled) return true; /* Read Netplay input, block if we're configured to stall for input every * frame */ if (netplay_data->stall_frames == 0 && netplay_data->read_frame_count <= netplay_data->self_frame_count) res = poll_input(netplay_data, true); else res = poll_input(netplay_data, false); if (res == -1) { hangup(netplay_data); return false; } /* Simulate the input if we don't have real input */ if (!netplay_data->buffer[netplay_data->self_ptr].have_remote) netplay_simulate_input(netplay_data, netplay_data->self_ptr); /* Consider stalling */ switch (netplay_data->stall) { case RARCH_NETPLAY_STALL_RUNNING_FAST: if (netplay_data->read_frame_count >= netplay_data->self_frame_count) netplay_data->stall = RARCH_NETPLAY_STALL_NONE; break; default: /* not stalling */ if (netplay_data->read_frame_count + netplay_data->stall_frames <= netplay_data->self_frame_count) { netplay_data->stall = RARCH_NETPLAY_STALL_RUNNING_FAST; netplay_data->stall_time = cpu_features_get_time_usec(); } } /* If we're stalling, consider disconnection */ if (netplay_data->stall) { retro_time_t now = cpu_features_get_time_usec(); /* Don't stall out while they're paused */ if (netplay_data->remote_paused) netplay_data->stall_time = now; else if (now - netplay_data->stall_time >= MAX_STALL_TIME_USEC) { /* Stalled out! */ hangup(netplay_data); return false; } } return true; }
int net_http_get(const char **result, size_t *size, const char *url, retro_time_t *timeout) { size_t length; uint8_t* data = NULL; char* res = NULL; int ret = NET_HTTP_GET_OK; struct http_t* http = NULL; retro_time_t t0 = cpu_features_get_time_usec(); struct http_connection_t *conn = net_http_connection_new(url); *result = NULL; /* Error creating the connection descriptor. */ if (!conn) goto error; /* Don't bother with timeouts here, it's just a string scan. */ while (!net_http_connection_iterate(conn)) {} /* Error finishing the connection descriptor. */ if (!net_http_connection_done(conn)) { ret = NET_HTTP_GET_MALFORMED_URL; goto error; } http = net_http_new(conn); /* Error connecting to the endpoint. */ if (!http) { ret = NET_HTTP_GET_CONNECT_ERROR; goto error; } while (!net_http_update(http, NULL, NULL)) { /* Timeout error. */ if (timeout && (cpu_features_get_time_usec() - t0) > *timeout) { ret = NET_HTTP_GET_TIMEOUT; goto error; } } data = net_http_data(http, &length, false); if (data) { res = (char*)malloc(length + 1); /* Allocation error. */ if ( !res ) goto error; memcpy((void*)res, (void*)data, length); free(data); res[length] = 0; *result = res; } else { length = 0; *result = NULL; } if (size) *size = length; error: if ( http ) net_http_delete( http ); if ( conn ) net_http_connection_free( conn ); if (timeout) { t0 = cpu_features_get_time_usec() - t0; if (t0 < *timeout) *timeout -= t0; else *timeout = 0; } return ret; }
/** * netplay_sync_post_frame * @netplay : pointer to netplay object * * Post-frame for Netplay synchronization. * We check if we have new input and replay from recorded input. */ void netplay_sync_post_frame(netplay_t *netplay, bool stalled) { uint32_t lo_frame_count, hi_frame_count; /* Unless we're stalling, we've just finished running a frame */ if (!stalled) { netplay->run_ptr = NEXT_PTR(netplay->run_ptr); netplay->run_frame_count++; } /* We've finished an input frame even if we're stalling */ if ((!stalled || netplay->stall == NETPLAY_STALL_INPUT_LATENCY) && netplay->self_frame_count < netplay->run_frame_count + netplay->input_latency_frames) { netplay->self_ptr = NEXT_PTR(netplay->self_ptr); netplay->self_frame_count++; } /* Only relevant if we're connected and not in a desynching operation */ if ((netplay->is_server && !netplay->connected_players) || (netplay->self_mode < NETPLAY_CONNECTION_CONNECTED) || (netplay->desync)) { netplay->other_frame_count = netplay->self_frame_count; netplay->other_ptr = netplay->self_ptr; /* FIXME: Duplication */ if (netplay->catch_up) { netplay->catch_up = false; input_driver_unset_nonblock_state(); driver_set_nonblock_state(); } return; } /* Reset if it was requested */ if (netplay->force_reset) { core_reset(); netplay->force_reset = false; } #ifndef DEBUG_NONDETERMINISTIC_CORES if (!netplay->force_rewind) { /* Skip ahead if we predicted correctly. * Skip until our simulation failed. */ while (netplay->other_frame_count < netplay->unread_frame_count && netplay->other_frame_count < netplay->run_frame_count) { struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr]; size_t i; for (i = 0; i < MAX_USERS; i++) { if (memcmp(ptr->simulated_input_state[i], ptr->real_input_state[i], sizeof(ptr->real_input_state[i])) != 0 && !ptr->used_real[i]) break; } if (i != MAX_USERS) break; netplay_handle_frame_hash(netplay, ptr); netplay->other_ptr = NEXT_PTR(netplay->other_ptr); netplay->other_frame_count++; } } #endif /* Now replay the real input if we've gotten ahead of it */ if (netplay->force_rewind || (netplay->other_frame_count < netplay->unread_frame_count && netplay->other_frame_count < netplay->run_frame_count)) { retro_ctx_serialize_info_t serial_info; /* Replay frames. */ netplay->is_replay = true; netplay->replay_ptr = netplay->other_ptr; netplay->replay_frame_count = netplay->other_frame_count; if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) /* Make sure we're initialized before we start loading things */ netplay_wait_and_init_serialization(netplay); serial_info.data = NULL; serial_info.data_const = netplay->buffer[netplay->replay_ptr].state; serial_info.size = netplay->state_size; if (!core_unserialize(&serial_info)) { RARCH_ERR("Netplay savestate loading failed: Prepare for desync!\n"); } while (netplay->replay_frame_count < netplay->run_frame_count) { retro_time_t start, tm; struct delta_frame *ptr = &netplay->buffer[netplay->replay_ptr]; serial_info.data = ptr->state; serial_info.size = netplay->state_size; serial_info.data_const = NULL; start = cpu_features_get_time_usec(); /* Remember the current state */ memset(serial_info.data, 0, serial_info.size); core_serialize(&serial_info); if (netplay->replay_frame_count < netplay->unread_frame_count) netplay_handle_frame_hash(netplay, ptr); /* Re-simulate this frame's input */ netplay_simulate_input(netplay, netplay->replay_ptr, true); autosave_lock(); core_run(); autosave_unlock(); netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); netplay->replay_frame_count++; #ifdef DEBUG_NONDETERMINISTIC_CORES if (ptr->have_remote && netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->replay_ptr], netplay->replay_frame_count)) { RARCH_LOG("PRE %u: %X\n", netplay->replay_frame_count-1, netplay_delta_frame_crc(netplay, ptr)); if (netplay->is_server) RARCH_LOG("INP %X %X\n", ptr->real_input_state[0], ptr->self_state[0]); else RARCH_LOG("INP %X %X\n", ptr->self_state[0], ptr->real_input_state[0]); ptr = &netplay->buffer[netplay->replay_ptr]; serial_info.data = ptr->state; memset(serial_info.data, 0, serial_info.size); core_serialize(&serial_info); RARCH_LOG("POST %u: %X\n", netplay->replay_frame_count-1, netplay_delta_frame_crc(netplay, ptr)); } #endif /* Get our time window */ tm = cpu_features_get_time_usec() - start; netplay->frame_run_time_sum -= netplay->frame_run_time[netplay->frame_run_time_ptr]; netplay->frame_run_time[netplay->frame_run_time_ptr] = tm; netplay->frame_run_time_sum += tm; netplay->frame_run_time_ptr++; if (netplay->frame_run_time_ptr >= NETPLAY_FRAME_RUN_TIME_WINDOW) netplay->frame_run_time_ptr = 0; } /* Average our time */ netplay->frame_run_time_avg = netplay->frame_run_time_sum / NETPLAY_FRAME_RUN_TIME_WINDOW; if (netplay->unread_frame_count < netplay->run_frame_count) { netplay->other_ptr = netplay->unread_ptr; netplay->other_frame_count = netplay->unread_frame_count; } else { netplay->other_ptr = netplay->run_ptr; netplay->other_frame_count = netplay->run_frame_count; } netplay->is_replay = false; netplay->force_rewind = false; } if (netplay->is_server) { uint32_t player; lo_frame_count = hi_frame_count = netplay->unread_frame_count; /* Look for players that are ahead of us */ for (player = 0; player < MAX_USERS; player++) { if (!(netplay->connected_players & (1<<player))) continue; if (netplay->read_frame_count[player] > hi_frame_count) hi_frame_count = netplay->read_frame_count[player]; } } else { lo_frame_count = hi_frame_count = netplay->server_frame_count; } /* If we're behind, try to catch up */ if (netplay->catch_up) { /* Are we caught up? */ if (netplay->self_frame_count + 1 >= lo_frame_count) { netplay->catch_up = false; input_driver_unset_nonblock_state(); driver_set_nonblock_state(); } } else if (!stalled) { if (netplay->self_frame_count + 3 < lo_frame_count) { retro_time_t cur_time = cpu_features_get_time_usec(); uint32_t cur_behind = lo_frame_count - netplay->self_frame_count; /* We're behind, but we'll only try to catch up if we're actually * falling behind, i.e. if we're more behind after some time */ if (netplay->catch_up_time == 0) { /* Record our current time to check for catch-up later */ netplay->catch_up_time = cur_time; netplay->catch_up_behind = cur_behind; } else if (cur_time - netplay->catch_up_time > CATCH_UP_CHECK_TIME_USEC) { /* Time to check how far behind we are */ if (netplay->catch_up_behind <= cur_behind) { /* We're definitely falling behind! */ netplay->catch_up = true; netplay->catch_up_time = 0; input_driver_set_nonblock_state(); driver_set_nonblock_state(); } else { /* Check again in another period */ netplay->catch_up_time = cur_time; netplay->catch_up_behind = cur_behind; } } } else if (netplay->self_frame_count + 3 < hi_frame_count) { size_t i; netplay->catch_up_time = 0; /* We're falling behind some clients but not others, so request that * clients ahead of us stall */ for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; int player; if (!connection->active || connection->mode != NETPLAY_CONNECTION_PLAYING) continue; player = connection->player; /* Are they ahead? */ if (netplay->self_frame_count + 3 < netplay->read_frame_count[player]) { /* Tell them to stall */ if (connection->stall_frame + NETPLAY_MAX_REQ_STALL_FREQUENCY < netplay->self_frame_count) { connection->stall_frame = netplay->self_frame_count; netplay_cmd_stall(netplay, connection, netplay->read_frame_count[player] - netplay->self_frame_count + 1); } } } } else netplay->catch_up_time = 0; } else netplay->catch_up_time = 0; }
bool menu_animation_ctl(enum menu_animation_ctl_state state, void *data) { static menu_animation_t anim; static retro_time_t cur_time = 0; static retro_time_t old_time = 0; static float delta_time = 0.0f; static bool animation_is_active = false; switch (state) { case MENU_ANIMATION_CTL_DEINIT: { size_t i; for (i = 0; i < anim.size; i++) { if (anim.list[i].subject) anim.list[i].subject = NULL; } free(anim.list); memset(&anim, 0, sizeof(menu_animation_t)); } cur_time = 0; old_time = 0; delta_time = 0.0f; break; case MENU_ANIMATION_CTL_IS_ACTIVE: return animation_is_active; case MENU_ANIMATION_CTL_CLEAR_ACTIVE: animation_is_active = false; break; case MENU_ANIMATION_CTL_SET_ACTIVE: animation_is_active = true; break; case MENU_ANIMATION_CTL_DELTA_TIME: { float *ptr = (float*)data; if (!ptr) return false; *ptr = delta_time; } break; case MENU_ANIMATION_CTL_UPDATE_TIME: { static retro_time_t last_clock_update = 0; settings_t *settings = config_get_ptr(); cur_time = cpu_features_get_time_usec(); delta_time = cur_time - old_time; if (delta_time >= IDEAL_DELTA_TIME* 4) delta_time = IDEAL_DELTA_TIME * 4; if (delta_time <= IDEAL_DELTA_TIME / 4) delta_time = IDEAL_DELTA_TIME / 4; old_time = cur_time; if (((cur_time - last_clock_update) > 1000000) && settings->menu.timedate_enable) { animation_is_active = true; last_clock_update = cur_time; } } break; case MENU_ANIMATION_CTL_UPDATE: { unsigned i; unsigned active_tweens = 0; float *dt = (float*)data; if (!dt) return false; for(i = 0; i < anim.size; i++) menu_animation_iterate(&anim, i, *dt, &active_tweens); if (!active_tweens) { anim.size = 0; anim.first_dead = 0; return false; } animation_is_active = true; } break; case MENU_ANIMATION_CTL_KILL_BY_TAG: { unsigned i; menu_animation_ctx_tag_t *tag = (menu_animation_ctx_tag_t*)data; if (!tag || tag->id == -1) return false; for (i = 0; i < anim.size; ++i) { if (anim.list[i].tag != tag->id) continue; anim.list[i].alive = false; anim.list[i].subject = NULL; if (i < anim.first_dead) anim.first_dead = i; } } break; case MENU_ANIMATION_CTL_KILL_BY_SUBJECT: { unsigned i, j, killed = 0; menu_animation_ctx_subject_t *subject = (menu_animation_ctx_subject_t*)data; float **sub = (float**)subject->data; for (i = 0; i < anim.size; ++i) { if (!anim.list[i].alive) continue; for (j = 0; j < subject->count; ++j) { if (anim.list[i].subject != sub[j]) continue; anim.list[i].alive = false; anim.list[i].subject = NULL; if (i < anim.first_dead) anim.first_dead = i; killed++; break; } } } break; case MENU_ANIMATION_CTL_TICKER: { menu_animation_ctx_ticker_t *ticker = (menu_animation_ctx_ticker_t*) data; size_t str_len = utf8len(ticker->str); size_t offset = 0; if ((size_t)str_len <= ticker->len) { utf8cpy(ticker->s, PATH_MAX_LENGTH, ticker->str, ticker->len); return true; } if (!ticker->selected) { utf8cpy(ticker->s, PATH_MAX_LENGTH, ticker->str, ticker->len - 3); strlcat(ticker->s, "...", PATH_MAX_LENGTH); return true; } menu_animation_ticker_generic( ticker->idx, ticker->len, &offset, &str_len); utf8cpy( ticker->s, PATH_MAX_LENGTH, utf8skip(ticker->str, offset), str_len); animation_is_active = true; } break; case MENU_ANIMATION_CTL_IDEAL_DELTA_TIME_GET: { menu_animation_ctx_delta_t *delta = (menu_animation_ctx_delta_t*)data; if (!delta) return false; delta->ideal = delta->current / IDEAL_DELTA_TIME; } break; case MENU_ANIMATION_CTL_PUSH: return menu_animation_push(&anim, data); case MENU_ANIMATION_CTL_NONE: default: break; } return true; }
int menu_popup_iterate(char *s, size_t len, const char *label) { #ifdef HAVE_CHEEVOS cheevos_ctx_desc_t desc_info; #endif bool do_exit = false; settings_t *settings = config_get_ptr(); switch (menu_popup_current_type) { case MENU_POPUP_WELCOME: { static int64_t timeout_end; int64_t timeout; static bool timer_begin = false; static bool timer_end = false; int64_t current = cpu_features_get_time_usec(); if (!timer_begin) { timeout_end = cpu_features_get_time_usec() + 3 /* seconds */ * 1000000; timer_begin = true; timer_end = false; } timeout = (timeout_end - current) / 1000000; menu_hash_get_help_enum( MENU_ENUM_LABEL_WELCOME_TO_RETROARCH, s, len); if (!timer_end && timeout <= 0) { timer_end = true; timer_begin = false; timeout_end = 0; do_exit = true; } } break; case MENU_POPUP_HELP_CONTROLS: { unsigned i; char s2[PATH_MAX_LENGTH] = {0}; const unsigned binds[] = { RETRO_DEVICE_ID_JOYPAD_UP, RETRO_DEVICE_ID_JOYPAD_DOWN, RETRO_DEVICE_ID_JOYPAD_A, RETRO_DEVICE_ID_JOYPAD_B, RETRO_DEVICE_ID_JOYPAD_SELECT, RETRO_DEVICE_ID_JOYPAD_START, RARCH_MENU_TOGGLE, RARCH_QUIT_KEY, RETRO_DEVICE_ID_JOYPAD_X, RETRO_DEVICE_ID_JOYPAD_Y, }; char desc[ARRAY_SIZE(binds)][64] = {{0}}; for (i = 0; i < ARRAY_SIZE(binds); i++) { const struct retro_keybind *keybind = (const struct retro_keybind*) &settings->input.binds[0][binds[i]]; const struct retro_keybind *auto_bind = (const struct retro_keybind*) input_get_auto_bind(0, binds[i]); input_config_get_bind_string(desc[i], keybind, auto_bind, sizeof(desc[i])); } menu_hash_get_help_enum(MENU_ENUM_LABEL_VALUE_MENU_ENUM_CONTROLS_PROLOG, s2, sizeof(s2)); snprintf(s, len, "%s" "[%s]: " "%-20s\n" "[%s]: " "%-20s\n" "[%s]: " "%-20s\n" "[%s]: " "%-20s\n" "[%s]: " "%-20s\n" "[%s]: " "%-20s\n" "[%s]: " "%-20s\n" "[%s]: " "%-20s\n" "[%s]: " "%-20s\n", s2, msg_hash_to_str( MENU_ENUM_LABEL_VALUE_BASIC_MENU_CONTROLS_SCROLL_UP), desc[0], msg_hash_to_str( MENU_ENUM_LABEL_VALUE_BASIC_MENU_CONTROLS_SCROLL_DOWN), desc[1], msg_hash_to_str( MENU_ENUM_LABEL_VALUE_BASIC_MENU_CONTROLS_CONFIRM), desc[2], msg_hash_to_str( MENU_ENUM_LABEL_VALUE_BASIC_MENU_CONTROLS_BACK), desc[3], msg_hash_to_str( MENU_ENUM_LABEL_VALUE_BASIC_MENU_CONTROLS_INFO), desc[4], msg_hash_to_str( MENU_ENUM_LABEL_VALUE_BASIC_MENU_CONTROLS_START), desc[5], msg_hash_to_str( MENU_ENUM_LABEL_VALUE_BASIC_MENU_CONTROLS_TOGGLE_MENU), desc[6], msg_hash_to_str( MENU_ENUM_LABEL_VALUE_BASIC_MENU_CONTROLS_QUIT), desc[7], msg_hash_to_str( MENU_ENUM_LABEL_VALUE_BASIC_MENU_CONTROLS_TOGGLE_KEYBOARD), desc[8] ); } break; #ifdef HAVE_CHEEVOS case MENU_POPUP_HELP_CHEEVOS_DESCRIPTION: desc_info.idx = menu_popup_current_id; desc_info.s = s; desc_info.len = len; cheevos_get_description(&desc_info); break; #endif case MENU_POPUP_HELP_WHAT_IS_A_CORE: menu_hash_get_help_enum(MENU_ENUM_LABEL_VALUE_WHAT_IS_A_CORE_DESC, s, len); break; case MENU_POPUP_HELP_LOADING_CONTENT: menu_hash_get_help_enum(MENU_ENUM_LABEL_LOAD_CONTENT_LIST, s, len); break; case MENU_POPUP_HELP_CHANGE_VIRTUAL_GAMEPAD: menu_hash_get_help_enum( MENU_ENUM_LABEL_VALUE_HELP_CHANGE_VIRTUAL_GAMEPAD_DESC, s, len); break; case MENU_POPUP_HELP_AUDIO_VIDEO_TROUBLESHOOTING: menu_hash_get_help_enum( MENU_ENUM_LABEL_VALUE_HELP_AUDIO_VIDEO_TROUBLESHOOTING_DESC, s, len); break; case MENU_POPUP_HELP_SCANNING_CONTENT: menu_hash_get_help_enum(MENU_ENUM_LABEL_VALUE_HELP_SCANNING_CONTENT_DESC, s, len); break; case MENU_POPUP_HELP_EXTRACT: menu_hash_get_help_enum(MENU_ENUM_LABEL_VALUE_EXTRACTING_PLEASE_WAIT, s, len); if (settings->bundle_finished) { settings->bundle_finished = false; do_exit = true; } break; case MENU_POPUP_QUIT_CONFIRM: case MENU_POPUP_INFORMATION: case MENU_POPUP_QUESTION: case MENU_POPUP_WARNING: case MENU_POPUP_ERROR: menu_hash_get_help_enum(menu_popup_current_msg, s, len); break; case MENU_POPUP_NONE: default: break; } if (do_exit) { menu_popup_current_type = MENU_POPUP_NONE; return 1; } return 0; }
/** * netplay_poll: * @netplay : pointer to netplay object * * Polls network to see if we have anything new. If our * network buffer is full, we simply have to block * for new input data. * * Returns: true (1) if successful, otherwise false (0). **/ static bool netplay_poll(void) { int res; uint32_t client; size_t i; netplay_data->can_poll = false; if (!get_self_input_state(netplay_data)) goto catastrophe; /* If we're not connected, we're done */ if (netplay_data->self_mode == NETPLAY_CONNECTION_NONE) return true; /* Read Netplay input, block if we're configured to stall for input every * frame */ netplay_update_unread_ptr(netplay_data); if (netplay_data->stateless_mode && (netplay_data->connected_players>1) && netplay_data->unread_frame_count <= netplay_data->run_frame_count) res = netplay_poll_net_input(netplay_data, true); else res = netplay_poll_net_input(netplay_data, false); if (res == -1) goto catastrophe; /* Resolve and/or simulate the input if we don't have real input */ netplay_resolve_input(netplay_data, netplay_data->run_ptr, false); /* Handle any slaves */ if (netplay_data->is_server && netplay_data->connected_slaves) netplay_handle_slaves(netplay_data); netplay_update_unread_ptr(netplay_data); /* Figure out how many frames of input latency we should be using to hide * network latency */ if (netplay_data->frame_run_time_avg || netplay_data->stateless_mode) { /* FIXME: Using fixed 60fps for this calculation */ unsigned frames_per_frame = netplay_data->frame_run_time_avg ? (16666/netplay_data->frame_run_time_avg) : 0; unsigned frames_ahead = (netplay_data->run_frame_count > netplay_data->unread_frame_count) ? (netplay_data->run_frame_count - netplay_data->unread_frame_count) : 0; settings_t *settings = config_get_ptr(); unsigned input_latency_frames_min = settings->uints.netplay_input_latency_frames_min; unsigned input_latency_frames_max = input_latency_frames_min + settings->uints.netplay_input_latency_frames_range; /* Assume we need a couple frames worth of time to actually run the * current frame */ if (frames_per_frame > 2) frames_per_frame -= 2; else frames_per_frame = 0; /* Shall we adjust our latency? */ if (netplay_data->stateless_mode) { /* In stateless mode, we adjust up if we're "close" and down if we * have a lot of slack */ if (netplay_data->input_latency_frames < input_latency_frames_min || (netplay_data->unread_frame_count == netplay_data->run_frame_count + 1 && netplay_data->input_latency_frames < input_latency_frames_max)) { netplay_data->input_latency_frames++; } else if (netplay_data->input_latency_frames > input_latency_frames_max || (netplay_data->unread_frame_count > netplay_data->run_frame_count + 2 && netplay_data->input_latency_frames > input_latency_frames_min)) { netplay_data->input_latency_frames--; } } else if (netplay_data->input_latency_frames < input_latency_frames_min || (frames_per_frame < frames_ahead && netplay_data->input_latency_frames < input_latency_frames_max)) { /* We can't hide this much network latency with replay, so hide some * with input latency */ netplay_data->input_latency_frames++; } else if (netplay_data->input_latency_frames > input_latency_frames_max || (frames_per_frame > frames_ahead + 2 && netplay_data->input_latency_frames > input_latency_frames_min)) { /* We don't need this much latency (any more) */ netplay_data->input_latency_frames--; } } /* If we're stalled, consider unstalling */ switch (netplay_data->stall) { case NETPLAY_STALL_RUNNING_FAST: { if (netplay_data->unread_frame_count + NETPLAY_MAX_STALL_FRAMES - 2 > netplay_data->self_frame_count) { netplay_data->stall = NETPLAY_STALL_NONE; for (i = 0; i < netplay_data->connections_size; i++) { struct netplay_connection *connection = &netplay_data->connections[i]; if (connection->active && connection->stall) connection->stall = NETPLAY_STALL_NONE; } } break; } case NETPLAY_STALL_SPECTATOR_WAIT: if (netplay_data->self_mode == NETPLAY_CONNECTION_PLAYING || netplay_data->unread_frame_count > netplay_data->self_frame_count) netplay_data->stall = NETPLAY_STALL_NONE; break; case NETPLAY_STALL_INPUT_LATENCY: /* Just let it recalculate momentarily */ netplay_data->stall = NETPLAY_STALL_NONE; break; case NETPLAY_STALL_SERVER_REQUESTED: { /* See if the stall is done */ if (netplay_data->connections[0].stall_frame == 0) { /* Stop stalling! */ netplay_data->connections[0].stall = NETPLAY_STALL_NONE; netplay_data->stall = NETPLAY_STALL_NONE; } else { netplay_data->connections[0].stall_frame--; } break; } case NETPLAY_STALL_NO_CONNECTION: /* We certainly haven't fixed this */ break; default: /* not stalling */ break; } /* If we're not stalled, consider stalling */ if (!netplay_data->stall) { /* Have we not read enough latency frames? */ if (netplay_data->self_mode == NETPLAY_CONNECTION_PLAYING && netplay_data->connected_players && netplay_data->run_frame_count + netplay_data->input_latency_frames > netplay_data->self_frame_count) { netplay_data->stall = NETPLAY_STALL_INPUT_LATENCY; netplay_data->stall_time = 0; } /* Are we too far ahead? */ if (netplay_data->unread_frame_count + NETPLAY_MAX_STALL_FRAMES <= netplay_data->self_frame_count) { netplay_data->stall = NETPLAY_STALL_RUNNING_FAST; netplay_data->stall_time = cpu_features_get_time_usec(); /* Figure out who to blame */ if (netplay_data->is_server) { for (client = 1; client < MAX_CLIENTS; client++) { struct netplay_connection *connection; if (!(netplay_data->connected_players & (1<<client))) continue; if (netplay_data->read_frame_count[client] > netplay_data->unread_frame_count) continue; connection = &netplay_data->connections[client-1]; if (connection->active && connection->mode == NETPLAY_CONNECTION_PLAYING) { connection->stall = NETPLAY_STALL_RUNNING_FAST; connection->stall_time = netplay_data->stall_time; } } } } /* If we're a spectator, are we ahead at all? */ if (!netplay_data->is_server && (netplay_data->self_mode == NETPLAY_CONNECTION_SPECTATING || netplay_data->self_mode == NETPLAY_CONNECTION_SLAVE) && netplay_data->unread_frame_count <= netplay_data->self_frame_count) { netplay_data->stall = NETPLAY_STALL_SPECTATOR_WAIT; netplay_data->stall_time = cpu_features_get_time_usec(); } } /* If we're stalling, consider disconnection */ if (netplay_data->stall && netplay_data->stall_time) { retro_time_t now = cpu_features_get_time_usec(); /* Don't stall out while they're paused */ if (netplay_data->remote_paused) netplay_data->stall_time = now; else if (now - netplay_data->stall_time >= (netplay_data->is_server ? MAX_SERVER_STALL_TIME_USEC : MAX_CLIENT_STALL_TIME_USEC)) { /* Stalled out! */ if (netplay_data->is_server) { for (i = 0; i < netplay_data->connections_size; i++) { struct netplay_connection *connection = &netplay_data->connections[i]; if (connection->active && connection->mode == NETPLAY_CONNECTION_PLAYING && connection->stall && now - connection->stall_time >= MAX_SERVER_STALL_TIME_USEC) { netplay_hangup(netplay_data, connection); } } } else goto catastrophe; return false; } } return true; catastrophe: for (i = 0; i < netplay_data->connections_size; i++) netplay_hangup(netplay_data, &netplay_data->connections[i]); return false; }
static bool video_thread_frame(void *data, const void *frame_, unsigned width, unsigned height, uint64_t frame_count, unsigned pitch, const char *msg) { unsigned copy_stride; static struct retro_perf_counter thr_frame = {0}; const uint8_t *src = NULL; uint8_t *dst = NULL; thread_video_t *thr = (thread_video_t*)data; /* If called from within read_viewport, we're actually in the * driver thread, so just render directly. */ if (thr->frame.within_thread) { thread_update_driver_state(thr); if (thr->driver && thr->driver->frame) return thr->driver->frame(thr->driver_data, frame_, width, height, frame_count, pitch, msg); return false; } performance_counter_init(&thr_frame, "thr_frame"); performance_counter_start(&thr_frame); copy_stride = width * (thr->info.rgb32 ? sizeof(uint32_t) : sizeof(uint16_t)); src = (const uint8_t*)frame_; dst = thr->frame.buffer; slock_lock(thr->lock); if (!thr->nonblock) { settings_t *settings = config_get_ptr(); retro_time_t target_frame_time = (retro_time_t) roundf(1000000 / settings->video.refresh_rate); retro_time_t target = thr->last_time + target_frame_time; /* Ideally, use absolute time, but that is only a good idea on POSIX. */ while (thr->frame.updated) { retro_time_t current = cpu_features_get_time_usec(); retro_time_t delta = target - current; if (delta <= 0) break; if (!scond_wait_timeout(thr->cond_cmd, thr->lock, delta)) break; } } /* Drop frame if updated flag is still set, as thread is * still working on last frame. */ if (!thr->frame.updated) { if (src) { unsigned h; for (h = 0; h < height; h++, src += pitch, dst += copy_stride) memcpy(dst, src, copy_stride); } thr->frame.updated = true; thr->frame.width = width; thr->frame.height = height; thr->frame.count = frame_count; thr->frame.pitch = copy_stride; if (msg) strlcpy(thr->frame.msg, msg, sizeof(thr->frame.msg)); else *thr->frame.msg = '\0'; scond_signal(thr->cond_thread); #if defined(HAVE_MENU) if (thr->texture.enable) { while (thr->frame.updated) scond_wait(thr->cond_cmd, thr->lock); } #endif thr->hit_count++; } else thr->miss_count++; slock_unlock(thr->lock); performance_counter_stop(&thr_frame); thr->last_time = cpu_features_get_time_usec(); return true; }
/** * runloop_iterate: * * Run Libretro core in RetroArch for one frame. * * Returns: 0 on success, 1 if we have to wait until * button input in order to wake up the loop, * -1 if we forcibly quit out of the RetroArch iteration loop. **/ int runloop_iterate(unsigned *sleep_ms) { unsigned i; retro_time_t current, target, to_sleep_ms; static uint64_t last_input = 0; enum runloop_state runloop_status = RUNLOOP_STATE_NONE; static retro_time_t frame_limit_minimum_time = 0.0; static retro_time_t frame_limit_last_time = 0.0; settings_t *settings = config_get_ptr(); uint64_t current_input = menu_driver_ctl(RARCH_MENU_CTL_IS_ALIVE, NULL) ? input_menu_keys_pressed() : input_keys_pressed(); uint64_t old_input = last_input; last_input = current_input; if (runloop_frame_time_last_enable) { runloop_frame_time_last = 0; runloop_frame_time_last_enable = false; } if (runloop_set_frame_limit) { struct retro_system_av_info *av_info = video_viewport_get_system_av_info(); float fastforward_ratio = (settings->fastforward_ratio == 0.0f) ? 1.0f : settings->fastforward_ratio; frame_limit_last_time = cpu_features_get_time_usec(); frame_limit_minimum_time = (retro_time_t)roundf(1000000.0f / (av_info->timing.fps * fastforward_ratio)); runloop_set_frame_limit = false; } if (runloop_frame_time.callback) { /* Updates frame timing if frame timing callback is in use by the core. * Limits frame time if fast forward ratio throttle is enabled. */ retro_time_t current = cpu_features_get_time_usec(); retro_time_t delta = current - runloop_frame_time_last; bool is_locked_fps = (runloop_paused || input_driver_is_nonblock_state()) | !!recording_driver_get_data_ptr(); if (!runloop_frame_time_last || is_locked_fps) delta = runloop_frame_time.reference; if (!is_locked_fps && runloop_slowmotion) delta /= settings->slowmotion_ratio; runloop_frame_time_last = current; if (is_locked_fps) runloop_frame_time_last = 0; runloop_frame_time.callback(delta); } runloop_status = runloop_check_state(settings, current_input, old_input, sleep_ms); switch (runloop_status) { case RUNLOOP_STATE_QUIT: frame_limit_last_time = 0.0; command_event(CMD_EVENT_QUIT, NULL); return -1; case RUNLOOP_STATE_SLEEP: case RUNLOOP_STATE_END: case RUNLOOP_STATE_MENU_ITERATE: core_poll(); #ifdef HAVE_NETWORKING /* FIXME: This is an ugly way to tell Netplay this... */ netplay_driver_ctl(RARCH_NETPLAY_CTL_PAUSE, NULL); #endif if (runloop_status == RUNLOOP_STATE_SLEEP) *sleep_ms = 10; if (runloop_status == RUNLOOP_STATE_END) goto end; if (runloop_status == RUNLOOP_STATE_MENU_ITERATE) return 0; return 1; case RUNLOOP_STATE_ITERATE: case RUNLOOP_STATE_NONE: default: break; } autosave_lock(); if (bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL)) bsv_movie_ctl(BSV_MOVIE_CTL_SET_FRAME_START, NULL); camera_driver_ctl(RARCH_CAMERA_CTL_POLL, NULL); /* Update binds for analog dpad modes. */ for (i = 0; i < settings->input.max_users; i++) { if (!settings->input.analog_dpad_mode[i]) continue; input_push_analog_dpad(settings->input.binds[i], settings->input.analog_dpad_mode[i]); input_push_analog_dpad(settings->input.autoconf_binds[i], settings->input.analog_dpad_mode[i]); } if ((settings->video.frame_delay > 0) && !input_driver_is_nonblock_state()) retro_sleep(settings->video.frame_delay); core_run(); #ifdef HAVE_CHEEVOS cheevos_test(); #endif for (i = 0; i < settings->input.max_users; i++) { if (!settings->input.analog_dpad_mode[i]) continue; input_pop_analog_dpad(settings->input.binds[i]); input_pop_analog_dpad(settings->input.autoconf_binds[i]); } if (bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL)) bsv_movie_ctl(BSV_MOVIE_CTL_SET_FRAME_END, NULL); autosave_unlock(); if (!settings->fastforward_ratio) return 0; end: current = cpu_features_get_time_usec(); target = frame_limit_last_time + frame_limit_minimum_time; to_sleep_ms = (target - current) / 1000; if (to_sleep_ms > 0) { *sleep_ms = (unsigned)to_sleep_ms; /* Combat jitter a bit. */ frame_limit_last_time += frame_limit_minimum_time; return 1; } frame_limit_last_time = cpu_features_get_time_usec(); return 0; }