Implement Graphics.play_movie in libretro builds

This commit is contained in:
刘皓 2025-05-03 18:27:44 -04:00
parent 23bc6625f1
commit 74e5cc763c
No known key found for this signature in database
GPG key ID: 7901753DB465B711
10 changed files with 301 additions and 86 deletions

View file

@ -328,6 +328,7 @@ static VALUE play_movie(int32_t argc, wasm_ptr_t argv, VALUE self) {
struct coro : boost::asio::coroutine {
wasm_ptr_t str;
int32_t volume;
bool skippable;
VALUE operator()(int32_t argc, wasm_ptr_t argv, VALUE self) {
BOOST_ASIO_CORO_REENTER (this) {
@ -338,13 +339,21 @@ static VALUE play_movie(int32_t argc, wasm_ptr_t argv, VALUE self) {
} else {
volume = 100;
}
bool skippable = argc >= 3 ? SANDBOX_VALUE_TO_BOOL(((VALUE *)(**sb() + argv))[2]) : false;
// TODO: use SANDBOX_YIELD as required
GFX_GUARD_EXC(shState->graphics().playMovie((const char *)(**sb() + str), volume, skippable));
skippable = argc >= 3 ? SANDBOX_VALUE_TO_BOOL(((VALUE *)(**sb() + argv))[2]) : false;
GFX_GUARD_EXC(sb().set_movie(shState->graphics().playMovie((const char *)(**sb() + str), volume, skippable)););
while (sb().get_movie_from_main_thread() != nullptr) {
SANDBOX_YIELD;
GFX_GUARD_EXC(sb().set_movie(shState->graphics().playMovie(sb().get_movie_from_main_thread())););
}
}
return SANDBOX_NIL;
}
~coro() {
GFX_GUARD_EXC(sb().set_movie(nullptr););
}
};
return sb()->bind<struct coro>()()(argc, argv, self);

View file

@ -57,7 +57,7 @@ void sandbox::sandbox_free(usize ptr) {
w2c_ruby_mkxp_sandbox_free(RB, ptr);
}
sandbox::sandbox() : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby)), bindings(ruby), yielding(false), transitioning(false) {
sandbox::sandbox() : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby)), bindings(ruby), movie(nullptr), yielding(false), transitioning(false) {
// Initialize the sandbox
wasm2c_ruby_instantiate(RB, wasi.get());
w2c_ruby_mkxp_sandbox_init(
@ -133,9 +133,34 @@ sandbox::sandbox() : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby)), bindings
}
sandbox::~sandbox() {
set_movie(nullptr);
if (yielding) {
w2c_ruby_asyncify_stop_unwind(ruby.get());
}
bindings.reset(); // Destroy the bindings before destroying the runtime since the bindings destructor requires the runtime to be alive
wasm2c_ruby_free(RB);
}
Movie *sandbox::get_movie_from_main_thread() {
return movie.load(std::memory_order_relaxed); // No need for synchronization because we always set the movie from the main thread
}
Movie *sandbox::get_movie_from_audio_thread() {
return movie.load(std::memory_order_seq_cst);
}
void sandbox::set_movie(Movie *new_movie) {
Movie *old_movie = get_movie_from_main_thread();
if (old_movie == new_movie) {
return;
}
if (old_movie != nullptr) {
movie.store(nullptr, std::memory_order_seq_cst);
AudioMutexGuard guard(movie_mutex);
Graphics::stopMovie(old_movie);
}
if (new_movie != nullptr) {
movie.store(new_movie, std::memory_order_seq_cst);
}
}

View file

@ -22,10 +22,13 @@
#ifndef MKXPZ_SANDBOX_H
#define MKXPZ_SANDBOX_H
#include <atomic>
#include <memory>
#include <boost/optional.hpp>
#include <mkxp-sandbox-bindgen.h>
#include "types.h"
#include "audio.h"
#include "graphics.h"
#define SANDBOX_AWAIT(coroutine, ...) \
do { \
@ -78,16 +81,21 @@ namespace mkxp_sandbox {
std::shared_ptr<struct w2c_ruby> ruby;
std::unique_ptr<struct w2c_wasi__snapshot__preview1> wasi;
boost::optional<struct mkxp_sandbox::bindings> bindings;
std::atomic<Movie *> movie;
bool yielding;
usize sandbox_malloc(usize size);
void sandbox_free(usize ptr);
public:
AudioMutex movie_mutex;
bool transitioning;
inline struct mkxp_sandbox::bindings &operator*() noexcept { return *bindings; }
inline struct mkxp_sandbox::bindings *operator->() noexcept { return &*bindings; }
sandbox();
~sandbox();
Movie *get_movie_from_main_thread();
Movie *get_movie_from_audio_thread();
void set_movie(Movie *new_movie);
// Internal utility method of the `SANDBOX_YIELD` macro.
inline void _begin_yield() {

View file

@ -192,6 +192,18 @@ if not compilers['cpp'].has_header_symbol('thread', 'std::this_thread::yield')
global_args += '-DMKXPZ_NO_STD_THIS_THREAD_YIELD'
endif
if not compilers['cpp'].has_header_symbol('thread', 'std::this_thread::sleep_for')
global_args += '-DMKXPZ_NO_STD_THIS_THREAD_SLEEP_FOR'
if not compilers['cpp'].has_header_symbol('unistd.h', 'usleep')
global_args += '-DMKXPZ_NO_USLEEP'
if not compilers['cpp'].has_header_symbol('time.h', 'nanosleep')
global_args += '-DMKXPZ_NO_NANOSLEEP'
endif
endif
endif
if not compilers['cpp'].has_header('pthread.h') or not compilers['cpp'].links('''
#include <pthread.h>
int main(void) {

View file

@ -389,6 +389,13 @@ Audio::Audio(RGSSThreadData &rtData)
#ifdef MKXPZ_RETRO
void Audio::render() {
if (mkxp_retro::sandbox->get_movie_from_audio_thread() != nullptr) {
AudioMutexGuard guard(mkxp_retro::sandbox->movie_mutex);
/* We need to call `get_movie_from_audio_thread()` a second time to avoid race
* conditions where the movie gets destroyed after the first time we checked
* that the movie isn't null but before we lock the mutex */
Graphics::streamMovieAudioProc(mkxp_retro::sandbox->get_movie_from_audio_thread());
}
p->meWatchProc();
for (int i = 0; i < (int)p->bgmTracks.size(); i++) {
p->bgmTracks[i]->render();

View file

@ -41,14 +41,15 @@
#include "shader.h"
#include "sharedstate.h"
#include "texpool.h"
#ifndef MKXPZ_RETRO
# include "theoraplay/theoraplay.h"
#endif // MKXPZ_RETRO
#include "theoraplay/theoraplay.h"
#include "util.h"
#include "input.h"
#include "sprite.h"
#ifndef MKXPZ_RETRO
#ifdef MKXPZ_RETRO
# include <memory>
# include "mkxp-polyfill.h"
#else
# include <SDL.h>
# include <SDL_image.h>
# include <SDL_timer.h>
@ -80,7 +81,6 @@
#define MOVIE_AUDIO_BUFFER_SIZE 2048
#define AUDIO_BUFFER_LEN_MS 2000
#ifndef MKXPZ_RETRO
typedef struct AudioQueue
{
const THEORAPLAY_AudioPacket *audio;
@ -91,19 +91,45 @@ typedef struct AudioQueue
static long readMovie(THEORAPLAY_Io *io, void *buf, long buflen)
{
#ifdef MKXPZ_RETRO
std::shared_ptr<struct FileSystem::File> *f = (std::shared_ptr<struct FileSystem::File> *) io->userdata;
return (long) PHYSFS_readBytes((*f)->get(), buf, buflen);
#else
SDL_RWops *f = (SDL_RWops *) io->userdata;
return (long) SDL_RWread(f, buf, 1, buflen);
#endif // MKXPZ_RETRO
} // IoFopenRead
static void closeMovie(THEORAPLAY_Io *io)
{
#ifndef MKXPZ_RETRO
SDL_RWops *f = (SDL_RWops *) io->userdata;
SDL_RWclose(f);
#endif // MKXPZ_RETRO
free(io);
} // IoFopenClose
static void movieSleep(uint32_t milliseconds)
{
#ifdef MKXPZ_RETRO
mkxp_sleep_ms(milliseconds);
#else
SDL_Delay(milliseconds);
#endif // MKXPZ_RETRO
}
#ifdef MKXPZ_RETRO
template<class C, void (C::*func)()>
static void movieThreadFun(C *obj)
{
(obj->*func)();
}
#endif // MKXPZ_RETRO
struct Movie
{
THEORAPLAY_Decoder *decoder;
@ -113,20 +139,49 @@ struct Movie
bool hasAudio;
bool skippable;
Bitmap *videoBitmap;
#ifdef MKXPZ_RETRO
std::shared_ptr<struct FileSystem::File> srcOps;
#else
SDL_RWops srcOps;
SDL_Thread *audioThread;
#endif // MKXPZ_RETRO
AtomicFlag audioThreadTermReq;
volatile AudioQueue *audioQueueHead;
volatile AudioQueue *audioQueueTail;
ALuint audioSource;
ALuint alBuffers[STREAM_BUFS];
ALshort audioBuffer[MOVIE_AUDIO_BUFFER_SIZE];
SDL_mutex *audioMutex;
AudioMutex audioMutex;
Sprite *movieSprite;
Sprite *letterboxSprite;
Bitmap *letterbox;
uint64_t frameMs;
uint64_t baseTicks;
float volume;
bool openedAudio;
// Variables used by streamMovieAudioProc
ALint streamMovieAudioState;
ALint procBufs;
volatile AudioQueue *audioPacketAndOffset;
int channels;
int sampleRate;
float *sourceSamples;
ALuint samplesToProcess;
ALshort *sampleBuffer;
ALuint remainingSamples;
Movie(bool skippable_)
: decoder(0), audio(0), video(0), skippable(skippable_), videoBitmap(0), audioThread(0)
Movie(float volume, bool skippable_)
: decoder(0), audio(0), video(0), skippable(skippable_), videoBitmap(0),
#ifndef MKXPZ_RETRO
audioThread(0),
#endif // MKXPZ_RETRO
movieSprite(nullptr), letterboxSprite(nullptr), letterbox(nullptr), frameMs(0), baseTicks(-1), volume(volume), openedAudio(false),
streamMovieAudioState(0), procBufs(STREAM_BUFS)
{
audioThreadTermReq.set();
}
bool preparePlayback()
{
@ -134,7 +189,9 @@ struct Movie
// https://ffmpeg.org/doxygen/0.11/group__lavc__misc__pixfmt.html
THEORAPLAY_Io *io = (THEORAPLAY_Io *) malloc(sizeof (THEORAPLAY_Io));
if(!io) {
#ifndef MKXPZ_RETRO
SDL_RWclose(&srcOps);
#endif // MKXPZ_RETRO
return false;
}
@ -143,13 +200,15 @@ struct Movie
io->userdata = &srcOps;
decoder = THEORAPLAY_startDecode(io, DEF_MAX_VIDEO_FRAMES, THEORAPLAY_VIDFMT_RGBA);
if (!decoder) {
#ifndef MKXPZ_RETRO
SDL_RWclose(&srcOps);
#endif // MKXPZ_RETRO
return false;
}
// Wait until the decoder has parsed out some basic truths from the file.
while (!THEORAPLAY_isInitialized(decoder)) {
SDL_Delay(VIDEO_DELAY);
movieSleep(VIDEO_DELAY);
}
// Once we're initialized, we can tell if this file has audio and/or video.
@ -162,7 +221,7 @@ struct Movie
if ((THEORAPLAY_availableVideo(decoder) >= DEF_MAX_VIDEO_FRAMES)) {
break; // we'll never progress, there's no audio yet but we've prebuffered as much as we plan to.
}
SDL_Delay(VIDEO_DELAY);
movieSleep(VIDEO_DELAY);
}
}
@ -174,14 +233,14 @@ struct Movie
// Wait until we have video
while ((video = THEORAPLAY_getVideo(decoder)) == NULL) {
SDL_Delay(VIDEO_DELAY);
movieSleep(VIDEO_DELAY);
}
// Wait until we have audio, if applicable
audio = NULL;
if (hasAudio) {
while ((audio = THEORAPLAY_getAudio(decoder)) == NULL && THEORAPLAY_availableVideo(decoder) < DEF_MAX_VIDEO_FRAMES) {
SDL_Delay(VIDEO_DELAY);
movieSleep(VIDEO_DELAY);
}
}
// Create this Bitmap without a hires replacement, because we don't
@ -210,17 +269,16 @@ struct Movie
item->offset = 0;
item->next = NULL;
SDL_LockMutex(audioMutex);
AudioMutexGuard guard(audioMutex);
if (audioQueueTail) {
audioQueueTail->next = item;
} else {
audioQueueHead = item;
}
audioQueueTail = item;
SDL_UnlockMutex(audioMutex);
}
void bufferMovieAudio(THEORAPLAY_Decoder *decoder, const Uint32 now) {
void bufferMovieAudio(THEORAPLAY_Decoder *decoder, const uint32_t now) {
const THEORAPLAY_AudioPacket *audio;
while ((audio = THEORAPLAY_getAudio(decoder)) != NULL) {
queueAudioPacket(audio);
@ -230,25 +288,18 @@ struct Movie
}
}
void streamMovieAudio(){
ALint state = 0;
ALint procBufs = STREAM_BUFS;
volatile AudioQueue *audioPacketAndOffset;
int channels;
int sampleRate;
float *sourceSamples;
ALuint samplesToProcess;
ALshort *sampleBuffer;
ALuint remainingSamples;
bool streamMovieAudioProc() {
// Quit if audio thread terminate request has been made
if (audioThreadTermReq) return false;
while(true) {
while(procBufs--) {
// Quit if audio thread terminate request has been made
if (audioThreadTermReq) return;
if(procBufs > 0) {
--procBufs;
remainingSamples = MOVIE_AUDIO_BUFFER_SIZE;
sampleBuffer = audioBuffer;
SDL_LockMutex(audioMutex);
remainingSamples = MOVIE_AUDIO_BUFFER_SIZE;
sampleBuffer = audioBuffer;
{
AudioMutexGuard guard(audioMutex);
while(audioQueueHead && (remainingSamples > 0)) {
audioPacketAndOffset = audioQueueHead;
@ -283,27 +334,32 @@ struct Movie
}
if(!audioQueueHead) audioQueueTail = NULL;
SDL_UnlockMutex(audioMutex);
alBufferData(alBuffers[procBufs], channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, audioBuffer,
(MOVIE_AUDIO_BUFFER_SIZE - remainingSamples) * sizeof(ALshort), sampleRate);
alSourceQueueBuffers(audioSource, 1, &alBuffers[procBufs]);
alGetSourcei(audioSource, AL_SOURCE_STATE, &state);
if(state != AL_PLAYING) alSourcePlay(audioSource);
}
// Periodically check the buffers until one is available
while(true) {
// Quit if audio thread terminate request has been made
if (audioThreadTermReq) return;
alGetSourcei(audioSource, AL_BUFFERS_PROCESSED, &procBufs);
if(procBufs > 0) break;
SDL_Delay(AUDIO_SLEEP);
}
alSourceUnqueueBuffers(audioSource, procBufs, alBuffers);
alBufferData(alBuffers[procBufs], channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, audioBuffer,
(MOVIE_AUDIO_BUFFER_SIZE - remainingSamples) * sizeof(ALshort), sampleRate);
alSourceQueueBuffers(audioSource, 1, &alBuffers[procBufs]);
alGetSourcei(audioSource, AL_SOURCE_STATE, &streamMovieAudioState);
if(streamMovieAudioState != AL_PLAYING) alSourcePlay(audioSource);
}
// Periodically check the buffers until one is available
else {
alGetSourcei(audioSource, AL_BUFFERS_PROCESSED, &procBufs);
if(procBufs > 0) {
alSourceUnqueueBuffers(audioSource, procBufs, alBuffers);
} else {
#ifndef MKXPZ_RETRO
movieSleep(AUDIO_SLEEP);
#endif // MKXPZ_RETRO
}
}
return true;
}
void streamMovieAudio() {
while(streamMovieAudioProc());
}
bool startAudio(float volume)
@ -313,31 +369,45 @@ struct Movie
alSourcef(audioSource, AL_GAIN, volume);
audioThreadTermReq.clear();
audioMutex = SDL_CreateMutex();
queueAudioPacket(audio);
audio = NULL;
bufferMovieAudio(decoder, 0);
#ifndef MKXPZ_RETRO
audioThread = createSDLThread <Movie, &Movie::streamMovieAudio>(this, "movieaudio");
#endif // MKXPZ_RETRO
return true;
}
void play(float volume)
bool play()
{
Uint32 frameMs = 0;
Uint32 baseTicks = SDL_GetTicks();
bool openedAudio = false;
if (baseTicks == (uint64_t)-1) {
#ifdef MKXPZ_RETRO
baseTicks = mkxp_retro::get_ticks_ms();
#else
baseTicks = SDL_GetTicks();
#endif // MKXPZ_RETRO
}
while (THEORAPLAY_isDecoding(decoder)) {
// Check for reset/shutdown input
if(shState->graphics().updateMovieInput(this)) break;
// Check for attempted skip
if (skippable) {
#ifdef MKXPZ_RETRO // TODO: move into shState
mkxp_retro::input->update();
if (mkxp_retro::input->isTriggered(Input::C) || mkxp_retro::input->isTriggered(Input::B)) break;
#else
shState->input().update();
if (shState->input().isTriggered(Input::C) || shState->input().isTriggered(Input::B)) break;
#endif // MKXPZ_RETRO
}
const Uint32 now = SDL_GetTicks() - baseTicks;
#ifdef MKXPZ_RETRO
const uint64_t now = mkxp_retro::get_ticks_ms() - baseTicks;
#else
const uint64_t now = SDL_GetTicks() - baseTicks;
#endif // MKXPZ_RETRO
if (!video) {
video = THEORAPLAY_getVideo(decoder);
@ -359,7 +429,7 @@ struct Movie
}
if (video && (video->playms <= now)) {
frameMs = (video->fps == 0.0) ? 0 : ((Uint32) (1000.0 / video->fps));
frameMs = (video->fps == 0.0) ? 0 : ((uint64_t) (1000.0 / video->fps));
if ( frameMs && ((now - video->playms) >= frameMs) )
{
// Skip frames to catch up
@ -389,18 +459,29 @@ struct Movie
video = NULL;
} else {
#ifndef MKXPZ_RETRO
// Next video frame not yet ready, let the CPU breathe
SDL_Delay(VIDEO_DELAY);
movieSleep(VIDEO_DELAY);
#endif // MKXPZ_RETRO
}
if (openedAudio) {
bufferMovieAudio(decoder, now);
}
#ifdef MKXPZ_RETRO
return true;
#endif // MKXPZ_RETRO
}
return false;
}
~Movie()
{
if (letterbox) delete letterbox;
if (letterboxSprite) delete letterboxSprite;
if (movieSprite) delete movieSprite;
if (hasAudio) {
if (audioQueueTail) {
THEORAPLAY_freeAudio(audioQueueTail->audio);
@ -411,12 +492,13 @@ struct Movie
THEORAPLAY_freeAudio(audioQueueHead->audio);
}
audioQueueHead = NULL;
SDL_DestroyMutex(audioMutex);
audioThreadTermReq.set();
#ifndef MKXPZ_RETRO
if(audioThread) {
SDL_WaitThread(audioThread, 0);
audioThread = 0;
}
#endif // MKXPZ_RETRO
alSourceStop(audioSource);
alDeleteSources(1, &audioSource);
alDeleteBuffers(STREAM_BUFS, alBuffers);
@ -430,19 +512,30 @@ struct Movie
struct MovieOpenHandler : FileSystem::OpenHandler
{
#ifdef MKXPZ_RETRO
std::shared_ptr<struct FileSystem::File> *srcOps;
#else
SDL_RWops *srcOps;
#endif // MKXPZ_RETRO
#ifdef MKXPZ_RETRO
MovieOpenHandler(std::shared_ptr<struct FileSystem::File> &srcOps)
#else
MovieOpenHandler(SDL_RWops &srcOps)
#endif // MKXPZ_RETRO
: srcOps(&srcOps)
{}
#ifdef MKXPZ_RETRO
bool tryRead(std::shared_ptr<struct FileSystem::File> ops, const char *ext)
#else
bool tryRead(SDL_RWops &ops, const char *ext)
#endif // MKXPZ_RETRO
{
*srcOps = ops;
return true;
}
};
#endif // MKXPZ_RETRO
struct PingPong {
TEXFBO rt[2];
@ -1634,48 +1727,80 @@ void Graphics::resizeWindow(int width, int height, bool center) {
}
bool Graphics::updateMovieInput(Movie *movie) {
#ifdef MKXPZ_RETRO
return false; // TODO
#else
return p->threadData->rqTerm || p->threadData->rqReset;
#endif // MKXPZ_RETRO
}
void Graphics::playMovie(const char *filename, int volume_, bool skippable) {
#ifndef MKXPZ_RETRO
Movie *Graphics::playMovie(const char *filename, int volume_, bool skippable) {
if (shState->config().enableHires) {
Debug() << "BUG: High-res Graphics playMovie not implemented";
}
Movie *movie = new Movie(skippable);
Movie *movie = new Movie(volume_, skippable);
MovieOpenHandler handler(movie->srcOps);
#ifdef MKXPZ_RETRO // TODO: move into shState
{
std::string path("/mkxp-retro-game/");
path.append(filename);
mkxp_retro::fs->openRead(handler, path.c_str());
}
#else
shState->fileSystem().openRead(handler, filename);
#endif // MKXPZ_RETRO
float volume = volume_ * 0.01f;
if (movie->preparePlayback()) {
Sprite movieSprite;
movie->movieSprite = new Sprite;
// Currently this stretches to fit the screen. VX Ace behavior is to center it and let the edges run off
movieSprite.setBitmap(movie->videoBitmap);
movie->movieSprite->setBitmap(movie->videoBitmap);
double ratio = std::min((double)width() / movie->video->width, (double)height() / movie->video->height);
movieSprite.setZoomX(ratio);
movieSprite.setZoomY(ratio);
movieSprite.setX((width() / 2) - (movie->video->width * ratio / 2));
movieSprite.setY((height() / 2) - (movie->video->height * ratio / 2));
movie->movieSprite->setZoomX(ratio);
movie->movieSprite->setZoomY(ratio);
movie->movieSprite->setX((width() / 2) - (movie->video->width * ratio / 2));
movie->movieSprite->setY((height() / 2) - (movie->video->height * ratio / 2));
Sprite letterboxSprite;
Bitmap letterbox(width(), height());
letterbox.fillRect(0, 0, width(), height(), Vec4(0,0,0,255));
letterboxSprite.setBitmap(&letterbox);
movie->letterboxSprite = new Sprite;
movie->letterbox = new Bitmap(width(), height());
movie->letterbox->fillRect(0, 0, width(), height(), Vec4(0,0,0,255));
movie->letterboxSprite->setBitmap(movie->letterbox);
letterboxSprite.setZ(4999);
movieSprite.setZ(5001);
movie->letterboxSprite->setZ(4999);
movie->movieSprite->setZ(5001);
movie->play(volume);
if (movie->play()) {
return movie;
}
}
#ifndef MKXPZ_RETRO
delete movie;
#endif // MKXPZ_RETRO
return nullptr;
}
Movie *Graphics::playMovie(Movie *movie) {
if (movie == nullptr) {
return nullptr;
}
if (movie->play()) {
return movie;
}
#ifndef MKXPZ_RETRO
delete movie;
#endif // MKXPZ_RETRO
return nullptr;
}
void Graphics::stopMovie(Movie *movie) {
if (movie != nullptr) {
delete movie;
}
}
bool Graphics::streamMovieAudioProc(Movie *movie) {
return movie->streamMovieAudioProc();
}
void Graphics::screenshot(const char *filename) {

View file

@ -73,7 +73,10 @@ public:
void resizeWindow(int width, int height, bool center=false);
void drawMovieFrame(const THEORAPLAY_VideoFrame* video, Bitmap *videoBitmap);
bool updateMovieInput(Movie *movie);
void playMovie(const char *filename, int volume, bool skippable);
Movie *playMovie(const char *filename, int volume, bool skippable);
Movie *playMovie(Movie *movie);
static void stopMovie(Movie *movie);
static bool streamMovieAudioProc(Movie *movie);
void screenshot(const char *filename);
void reset();

View file

@ -406,7 +406,7 @@ main_source = files(
is_libretro ? 'audio/sndfilesource.cpp' : 'audio/sdlsoundsource.cpp',
'audio/soundemitter.cpp',
'audio/vorbissource.cpp',
is_libretro ? [] : 'theoraplay/theoraplay.c',
'theoraplay/theoraplay.c',
'crypto/rgssad.cpp',

View file

@ -27,6 +27,15 @@
# include <malloc.h>
#endif
#ifndef MKXPZ_NO_STD_THIS_THREAD_SLEEP_FOR
# include <thread>
#elif !defined(MKXPZ_NO_USLEEP)
# include <unistd.h>
#elif !defined(MKXPZ_NO_NANOSLEEP)
# include <time.h>
#endif
#ifdef MKXPZ_NO_SPRINTF
extern "C" int sprintf(char *buffer, const char *format, ...) {
va_list vlist;
@ -311,3 +320,17 @@ extern "C" int mkxp_sem_wait(mkxp_sem_t *sem) {
return 0;
#endif
}
extern "C" void mkxp_sleep_ms(uint32_t milliseconds) {
#ifndef MKXPZ_NO_STD_THIS_THREAD_SLEEP_FOR
std::this_thread::sleep_for(std::chrono::duration<uint32_t, std::milli>(milliseconds));
#elif !defined(MKXPZ_NO_USLEEP)
usleep((useconds_t)1000 * (useconds_t)milliseconds);
#elif !defined(MKXPZ_NO_NANOSLEEP)
struct timespec t;
t.tv_sec = milliseconds / 1000;
t.tv_nsec = milliseconds % 1000;
t.tv_nsec *= 1000000;
nanosleep(&t, nullptr);
#endif
}

View file

@ -26,6 +26,7 @@
#include <tgmath.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#ifndef MKXPZ_NO_PTHREAD_H
@ -131,6 +132,8 @@ int mkxp_sem_post(mkxp_sem_t *sem);
int mkxp_sem_wait(mkxp_sem_t *sem);
void mkxp_sleep_ms(uint32_t milliseconds);
#ifdef __cplusplus
}