blob: afc0474a8ba4e90b74b6e392a06dd429962031e7 [file] [log] [blame]
#include <chrono>
#include <iostream>
#include <random>
#include "SDL.h"
#include "File.hpp"
#include "System.hpp"
#include "Input.hpp"
#include "32blit.hpp"
#include "UserCode.hpp"
#include "JPEG.hpp"
#include "Multiplayer.hpp"
#include "engine/api_private.hpp"
extern Input *blit_input;
// blit audio channels
static blit::AudioChannel channels[CHANNEL_COUNT];
int System::width = System::max_width;
int System::height = System::max_height;
// blit framebuffer memory
static uint8_t framebuffer[System::max_width * System::max_height * 3];
static blit::Pen palette[256];
// blit debug callback
void blit_debug(const char *message) {
std::cout << message;
}
// blit screenmode callback
blit::ScreenMode _mode = blit::ScreenMode::lores;
static blit::ScreenMode requested_mode = blit::ScreenMode::lores;
static blit::PixelFormat cur_format = blit::PixelFormat::RGB;
static blit::PixelFormat requested_format = blit::PixelFormat::RGB;
blit::SurfaceInfo cur_surf_info;
static void set_screen_palette(const blit::Pen *colours, int num_cols) {
memcpy(palette, colours, num_cols * sizeof(blit::Pen));
}
static bool set_screen_mode_format(blit::ScreenMode new_mode, blit::SurfaceTemplate &new_surf_template) {
new_surf_template.data = framebuffer;
if(new_surf_template.format == (blit::PixelFormat)-1)
new_surf_template.format = blit::PixelFormat::RGB;
blit::Size default_bounds(System::width, System::height);
if(new_surf_template.bounds.empty())
new_surf_template.bounds = default_bounds;
switch(new_mode) {
case blit::ScreenMode::lores:
new_surf_template.bounds /= 2;
break;
case blit::ScreenMode::hires:
case blit::ScreenMode::hires_palette:
break;
}
if(new_surf_template.bounds != default_bounds && new_surf_template.bounds != default_bounds / 2)
return false;
switch(new_surf_template.format) {
case blit::PixelFormat::RGB:
case blit::PixelFormat::RGB565:
break;
case blit::PixelFormat::P:
new_surf_template.palette = palette;
break;
default:
return false;
}
requested_mode = new_mode;
requested_format = new_surf_template.format;
return true;
}
blit::SurfaceInfo &set_screen_mode(blit::ScreenMode new_mode) {
blit::SurfaceTemplate temp{nullptr, {0, 0}, new_mode == blit::ScreenMode::hires_palette ? blit::PixelFormat::P : blit::PixelFormat::RGB};
// won't fail for the modes used here
set_screen_mode_format(new_mode, temp);
cur_surf_info.data = temp.data;
cur_surf_info.bounds = temp.bounds;
cur_surf_info.format = temp.format;
cur_surf_info.palette = temp.palette;
return cur_surf_info;
}
// blit timer callback
std::chrono::steady_clock::time_point start;
uint32_t now() {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start);
return (uint32_t)elapsed.count();
}
// blit random callback
#ifdef __MINGW32__
// Windows/MinGW doesn't support a non-deterministic source of randomness, so we fall back upon the age-old time seed once more
// Without this, random_device() will always return the same number and thus our mersenne twister will always produce the same sequence.
std::mt19937 random_generator(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count());
#else
std::random_device random_device;
std::mt19937 random_generator(random_device());
#endif
std::uniform_int_distribution<uint32_t> random_distribution;
uint32_t blit_random() {
return random_distribution(random_generator);
}
// us timer used by profiler
void enable_us_timer()
{
// Enable/initialise timer
}
uint32_t get_us_timer()
{
// get current time in us
uint64_t ticksPerUs = SDL_GetPerformanceFrequency() / 1000000;
return SDL_GetPerformanceCounter() / ticksPerUs;
}
uint32_t get_max_us_timer()
{
// largest us value timer can produce for wrapping
return UINT32_MAX;
}
/* Added a command line ability to specify a launch_path parameter. */
extern const char *launch_path;
static const char *get_launch_path() {
return launch_path;
}
static blit::GameMetadata get_metadata() {
blit::GameMetadata ret;
ret.title = metadata_title;
ret.author = metadata_author;
ret.description = metadata_description;
ret.version = metadata_version;
ret.url = metadata_url;
ret.category = metadata_category;
return ret;
}
extern Multiplayer *blit_multiplayer;
bool blit_is_multiplayer_connected() {
return blit_multiplayer->is_connected();
}
void blit_set_multiplayer_enabled(bool enabled) {
blit_multiplayer->set_enabled(enabled);
}
void blit_send_message(const uint8_t *data, uint16_t length) {
blit_multiplayer->send_message(data, length);
}
// blit API
static const blit::APIConst blit_api_const {
blit::api_version_major, blit::api_version_minor,
channels,
::set_screen_mode,
::set_screen_palette,
::now,
::blit_random,
nullptr, // exit
::blit_debug,
::open_file,
::read_file,
::write_file,
::close_file,
::get_file_length,
::list_files,
::file_exists,
::directory_exists,
::create_directory,
::rename_file,
::remove_file,
::get_save_path,
::is_storage_available,
::enable_us_timer,
::get_us_timer,
::get_max_us_timer,
blit_decode_jpeg_buffer,
blit_decode_jpeg_file,
nullptr, // launch
nullptr, // erase_game
nullptr, // get_type_handler_metadata
::get_launch_path,
blit_is_multiplayer_connected,
blit_set_multiplayer_enabled,
blit_send_message,
nullptr, // flash_to_tmp
nullptr, // tmp_file_closed
::get_metadata,
::set_screen_mode_format,
nullptr, // i2c_send
nullptr, // i2c_recieve
nullptr, // set_raw_cdc_enabled
nullptr, // cdc_write
nullptr, // cdc_read
nullptr, // list_installed_games
nullptr, // can_launch
};
static blit::APIData blit_api_data;
namespace blit {
const APIConst &api = blit_api_const;
APIData &api_data = blit_api_data;
}
// SDL events
const Uint32 System::timer_event = SDL_RegisterEvents(2);
const Uint32 System::loop_event = System::timer_event + 1;
#ifndef __EMSCRIPTEN__
// Thread bouncers
static int system_timer_thread(void *ptr) {
// Bounce back in to the class.
System *sys = (System *)ptr;
return sys->timer_thread();
}
static int system_loop_thread(void *ptr) {
// Bounce back in to the class.
System *sys = (System *)ptr;
return sys->update_thread();
}
#endif
System::System() {
m_input = SDL_CreateMutex();
s_timer_stop = SDL_CreateSemaphore(0);
s_loop_update = SDL_CreateSemaphore(0);
s_loop_redraw = SDL_CreateSemaphore(0);
s_loop_ended = SDL_CreateSemaphore(0);
}
System::~System() {
SDL_DestroyMutex(m_input);
SDL_DestroySemaphore(s_timer_stop);
SDL_DestroySemaphore(s_loop_update);
SDL_DestroySemaphore(s_loop_redraw);
SDL_DestroySemaphore(s_loop_ended);
}
void System::run() {
running = true;
start = std::chrono::steady_clock::now();
blit::update = ::update;
blit::render = ::render;
setup_base_path();
blit::set_screen_mode(blit::lores);
#ifdef __EMSCRIPTEN__
::init();
#else
t_system_loop = SDL_CreateThread(system_loop_thread, "Loop", (void *)this);
t_system_timer = SDL_CreateThread(system_timer_thread, "Timer", (void *)this);
#endif
}
int System::timer_thread() {
// Signal the system loop every 10 msec.
int dropped = 0;
SDL_Event event = {};
event.type = timer_event;
while (SDL_SemWaitTimeout(s_timer_stop, 10)) {
if (SDL_SemValue(s_loop_update)) {
dropped++;
if(dropped > 100) {
dropped = 100;
event.user.code = 2;
SDL_PushEvent(&event);
} else {
event.user.code = 1;
SDL_PushEvent(&event);
}
} else {
SDL_SemPost(s_loop_update);
dropped = 0;
event.user.code = 0;
SDL_PushEvent(&event);
}
}
return 0;
}
int System::update_thread() {
// Run the blit user code once every time we are signalled.
SDL_Event event = {};
event.type = loop_event;
::init(); // Run init here because the user can make it hang.
while (true) {
SDL_SemWait(s_loop_update);
if(!running) break;
bool rendered = loop();
if(!running) break;
if(rendered) {
// present on main thread if we need to
SDL_PushEvent(&event);
SDL_SemWait(s_loop_redraw);
}
}
SDL_SemPost(s_loop_ended);
return 0;
}
bool System::loop() {
SDL_LockMutex(m_input);
blit::buttons = shadow_buttons;
blit::tilt.x = shadow_tilt[0];
blit::tilt.y = shadow_tilt[1];
blit::tilt.z = shadow_tilt[2];
blit::tilt.normalize();
blit::joystick.x = shadow_joystick[0];
blit::joystick.y = shadow_joystick[1];
SDL_UnlockMutex(m_input);
bool rendered = false;
// only render at 50Hz (main loop runs at 100Hz)
// however, the emscripten loop (usually) runs at the display refresh rate
auto time_now = ::now();
#ifndef __EMSCRIPTEN__
if(time_now - last_render_time >= 20)
#endif
{
blit::render(time_now);
last_render_time = time_now;
if(_mode != requested_mode || cur_format != requested_format) {
_mode = requested_mode;
cur_format = requested_format;
}
rendered = true;
}
blit::tick(::now());
blit_input->rumble_controllers(blit::vibration);
blit_multiplayer->update();
return rendered;
}
Uint32 System::mode() {
return _mode;
}
Uint32 System::format() {
return Uint32(cur_format);
}
void System::update_texture(SDL_Texture *texture) {
bool is_lores = _mode == blit::ScreenMode::lores;
SDL_Rect dest_rect{0, 0, is_lores ? width / 2 : width, is_lores ? height / 2 : height};
auto stride = dest_rect.w * blit::pixel_format_stride[int(cur_format)];
if(cur_format == blit::PixelFormat::P) {
uint8_t col_fb[max_width * max_height * 3];
auto in = framebuffer, out = col_fb;
auto size = dest_rect.w * dest_rect.h;
for(int i = 0; i < size; i++) {
uint8_t index = *(in++);
(*out++) = palette[index].r;
(*out++) = palette[index].g;
(*out++) = palette[index].b;
}
SDL_UpdateTexture(texture, &dest_rect, col_fb, stride * 3);
} else
SDL_UpdateTexture(texture, &dest_rect, framebuffer, stride);
}
void System::notify_redraw() {
SDL_SemPost(s_loop_redraw);
}
void System::set_joystick(int axis, float value) {
if (axis < 2) {
SDL_LockMutex(m_input);
shadow_joystick[axis] = value;
SDL_UnlockMutex(m_input);
}
}
void System::set_tilt(int axis, float value) {
if (axis < 3) {
SDL_LockMutex(m_input);
shadow_tilt[axis] = value;
SDL_UnlockMutex(m_input);
}
}
void System::set_button(int button, bool state) {
SDL_LockMutex(m_input);
if (state) {
shadow_buttons |= button;
} else {
shadow_buttons &= ~button;
}
SDL_UnlockMutex(m_input);
}
void System::stop() {
int returnValue;
running = false;
// make sure the update thread is not waiting for a render to complete
SDL_SemPost(s_loop_redraw);
if(SDL_SemWaitTimeout(s_loop_ended, 500)) {
std::cerr << "User code appears to have frozen. Detaching thread." << std::endl;
SDL_DetachThread(t_system_loop);
} else {
SDL_WaitThread(t_system_loop, &returnValue);
}
SDL_SemPost(s_timer_stop);
SDL_WaitThread(t_system_timer, &returnValue);
}