Skip to content

Commit

Permalink
Draw and play each frame
Browse files Browse the repository at this point in the history
  • Loading branch information
Namaneo committed Sep 10, 2023
1 parent 494f9ce commit d32461c
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 148 deletions.
4 changes: 2 additions & 2 deletions app/GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ DEFINES := \
LDFLAGS := \
-lwasi-emulated-mman -fwasm-exceptions \
-mexec-model=reactor -Wl,--no-entry \
$(EXPORTS) -Wl,--export=calloc,--export=free \
-Wl,--export=calloc,--export=free \
-Wl,--initial-memory=$$((200 * 1024 * 1024)) \
-Wl,--max-memory=$$((600 * 1024 * 1024)) \
-Wl,--import-memory,--export-memory
Expand All @@ -31,7 +31,7 @@ prepare:

$(OUT_DIR)/%.wasm:
@echo Building $*.wasm...
@$(CXX) -o $(OUT_DIR)/$*.wasm junie.o $(DEPS) $(CORES_DIR)/lib$*.a $(LDFLAGS) $(QUIET)
@$(CXX) -o $(OUT_DIR)/$*.wasm junie.o $(DEPS) $(CORES_DIR)/lib$*.a $(LDFLAGS) $(EXPORTS) $(QUIET)

clean:
@rm -rf junie.o $(OUT_DIR)
Expand Down
7 changes: 0 additions & 7 deletions app/exports.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ JunieCreate
JunieStartGame
JunieDestroy

JunieLock
JunieUnlock

JunieGetVideo
JunieGetAudio
JunieGetVariables

JunieSetAudio
JunieSetSpeed
JunieSetInput
Expand Down
92 changes: 48 additions & 44 deletions app/junie.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,23 @@ typedef enum {
JUN_PATH_MAX = 7,
} JUN_PathType;

typedef struct {
void *data;
enum retro_pixel_format format;
uint32_t width;
uint32_t height;
uint32_t pitch;
float ratio;
} JunieVideo;

typedef struct {
void *data;
float rate;
size_t frames;
size_t size;
bool enable;
} JunieAudio;

struct jun_core_sym {
void *library;
bool initialized;
Expand Down Expand Up @@ -422,23 +439,6 @@ static void create_paths(const char *system, const char *rom)
free(game);
}

void JunieCreate(const char *system, const char *rom)
{
setbuf(stdout, NULL);

CTX.speed = 1;

create_paths(system, rom);
initialize_symbols();

CTX.sym.retro_set_environment(environment);
CTX.sym.retro_set_video_refresh(video_refresh);
CTX.sym.retro_set_input_poll(input_poll);
CTX.sym.retro_set_input_state(input_state);
CTX.sym.retro_set_audio_sample(audio_sample);
CTX.sym.retro_set_audio_sample_batch(audio_sample_batch);
}

static void core_lock()
{
slock_lock(CTX.mutex);
Expand Down Expand Up @@ -539,6 +539,11 @@ static void restore_memories()
restore_memory(RETRO_MEMORY_RTC, rtc_path);
}

#define IMPORT(name) __attribute__((import_module("env"), import_name(#name))) name
void IMPORT(junie_interop_video)(const JunieVideo *video);
void IMPORT(junie_interop_audio)(const JunieAudio *audio);
void IMPORT(junie_interop_variables)(const JunieVariable *variables);

static void core_thread(void *opaque)
{
while (!CTX.destroying) {
Expand All @@ -550,6 +555,13 @@ static void core_thread(void *opaque)
core_lock();
CTX.sym.retro_run();
core_unlock();

CTX.audio.rate = (float) CTX.av.timing.sample_rate * CTX.speed;

junie_interop_video(&CTX.video);
junie_interop_audio(&CTX.audio);

CTX.audio.frames = 0;
}
}

Expand All @@ -562,6 +574,25 @@ static void memory_thread(void *opaque)
save_memories();
}

void JunieCreate(const char *system, const char *rom)
{
setbuf(stdout, NULL);

CTX.speed = 1;

create_paths(system, rom);
initialize_symbols();

CTX.sym.retro_set_environment(environment);
CTX.sym.retro_set_video_refresh(video_refresh);
CTX.sym.retro_set_input_poll(input_poll);
CTX.sym.retro_set_input_state(input_state);
CTX.sym.retro_set_audio_sample(audio_sample);
CTX.sym.retro_set_audio_sample_batch(audio_sample_batch);

junie_interop_variables(CTX.variables);
}

bool JunieStartGame()
{
CTX.sym.retro_init();
Expand Down Expand Up @@ -630,33 +661,6 @@ void JunieDestroy()
CTX = (struct CTX) {0};
}

void JunieLock()
{
core_lock();
CTX.audio.rate = (float) CTX.av.timing.sample_rate * CTX.speed;
}

void JunieUnlock()
{
CTX.audio.frames = 0;
core_unlock();
}

const JunieVideo *JunieGetVideo()
{
return &CTX.video;
}

const JunieAudio *JunieGetAudio()
{
return &CTX.audio;
}

const JunieVariable *JunieGetVariables()
{
return CTX.variables;
}

void JunieSetAudio(bool enable)
{
CTX.audio.enable = enable;
Expand Down
21 changes: 0 additions & 21 deletions app/junie.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

#include "libretro.h"

typedef enum retro_pixel_format JuniePixelFormat;

typedef enum {
JUN_DEVICE_JOYPAD = RETRO_DEVICE_JOYPAD,
JUN_DEVICE_POINTER = RETRO_DEVICE_POINTER,
Expand Down Expand Up @@ -33,23 +31,6 @@ typedef enum {
JUN_DEVICE_ID_POINTER_COUNT = RETRO_DEVICE_ID_POINTER_COUNT,
} JunieInputID;

typedef struct {
void *data;
JuniePixelFormat format;
uint32_t width;
uint32_t height;
uint32_t pitch;
float ratio;
} JunieVideo;

typedef struct {
void *data;
float rate;
size_t frames;
size_t size;
bool enable;
} JunieAudio;

typedef struct {
char *key;
char *value;
Expand All @@ -70,8 +51,6 @@ void JunieDestroy();
void JunieLock();
void JunieUnlock();

const JunieVideo *JunieGetVideo();
const JunieAudio *JunieGetAudio();
const JunieVariable *JunieGetVariables();

void JunieSetSpeed(uint8_t speed);
Expand Down
2 changes: 1 addition & 1 deletion ui/sources/hooks/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export const useCore = (lib) => {
setSettings(settings);
setCheats(cheats);

setVariables(await core.create(system, rom, canvas));
await core.create(system, rom, canvas, setVariables);
await core.start(settings, cheats);

initAudio(settings);
Expand Down
10 changes: 7 additions & 3 deletions ui/sources/services/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ export default class Core {
* @param {string} system
* @param {string} rom
* @param {HTMLCanvasElement} canvas
* @returns {Promise<Variable[]>}
* @param {(variables: Variable[]) => void} on_variables
* @returns {Promise<void>}
*/
async create(system, rom, canvas) {
async create(system, rom, canvas, on_variables) {
const graphics = new Graphics(canvas);

const origin = location.origin + location.pathname.substring(0, location.pathname.lastIndexOf('/'));
Expand All @@ -64,12 +65,15 @@ export default class Core {
case 'audio':
AudioPlayer.queue(message.data.view, message.data.sample_rate);
break;
case 'variables':
on_variables(message.data.variables);
break;
}
}

this.#parallel = new Parallel(Interop, false, handler);
this.#interop = await this.#parallel.create(this.#name, script);
return await this.#interop.init(await Files.clone(), config);
await this.#interop.init(await Files.clone(), config);
}

/**
Expand Down
102 changes: 33 additions & 69 deletions ui/sources/services/interop.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,6 @@ export default class Interop {
/** @type {WebAssembly.Instance} */
#instance = null;

/** @type {Promise} */
#running = null;

/** @type {boolean} */
#stop = false;

/** @type {number} */
#video = 0;

/** @type {number} */
#audio = 0;

/**
* @param {WebAssembly.Instance} instance
* @param {('number' | 'string' | 'boolean' | null)} type
Expand Down Expand Up @@ -122,15 +110,40 @@ export default class Interop {
/**
* @param {MessagePort} fs_port
* @param {InteropConfig} config
* @returns {Promise<Variable[]>}
* @returns {Promise<void>}
*/
async init(fs_port, config) {
const fs_parallel = new Parallel(Filesystem, true);
const filesystem = fs_parallel.link(fs_port);
this.#wasi = new WASI(config.memory, filesystem, config.fds);

const junie_interop_video = (video_c) => {
const video = Video.parse(config.memory, video_c);
if (!video.data)
return;

const video_view = video.format == 1
? new Uint8Array(config.memory.buffer, video.data, video.pitch * video.height).slice()
: new Uint16Array(config.memory.buffer, video.data, (video.pitch * video.height) / 2).slice();
postMessage({ type: 'video', view: video_view, video }, [video_view.buffer]);
};

const junie_interop_audio = (audio_c) => {
const audio = Audio.parse(config.memory, audio_c);
if (!audio.frames)
return;

const audio_view = new Float32Array(config.memory.buffer, audio.data, audio.frames * 2).slice();
postMessage({ type: 'audio', view: audio_view, sample_rate: audio.rate }, [audio_view.buffer]);
};

const junie_interop_variables = (variables_c) => {
const variables = Variable.parse(this.#instance, variables_c);
postMessage({ type: 'variables', variables });
}

const source = await WebAssembly.instantiateStreaming(fetch(`${config.origin}/modules/${config.core}.wasm`), {
env: { memory: config.memory },
env: { memory: config.memory, junie_interop_video, junie_interop_audio, junie_interop_variables },
wasi_snapshot_preview1: this.#wasi.environment,
wasi: { 'thread-spawn': (start_arg) => {
const id = filesystem.id();
Expand All @@ -153,13 +166,6 @@ export default class Interop {
this.#wrap('StartGame', 'boolean', []);
this.#wrap('Destroy', null, []);

this.#wrap('Lock', null, []);
this.#wrap('Unlock', null, []);

this.#wrap('GetVideo', 'number', []);
this.#wrap('GetAudio', 'number', []);
this.#wrap('GetVariables', 'number', []);

this.#wrap('SetAudio', null, ['boolean']);
this.#wrap('SetSpeed', null, ['number']);
this.#wrap('SetInput', null, ['number', 'number', 'number']);
Expand All @@ -170,54 +176,6 @@ export default class Interop {
this.#wrap('RestoreState', null, []);

this.Create(config.system, config.rom);

this.#video = this.GetVideo();
this.#audio = this.GetAudio();

return Variable.parse(this.#instance, this.GetVariables());
}

/** @returns {Promise<void>} */
start() {
this.StartGame();

const memory = this.#instance.exports.memory;

this.#stop = false;
this.#running = new Promise(resolve => {
const step = async () => {
this.#stop ? resolve() : requestAnimationFrame(step);

this.Lock();

const video = Video.parse(memory, this.#video);
const audio = Audio.parse(memory, this.#audio);

if (video.data) {
const video_view = video.format == 1
? new Uint8Array(memory.buffer, video.data, video.pitch * video.height).slice()
: new Uint16Array(memory.buffer, video.data, (video.pitch * video.height) / 2).slice();
postMessage({ type: 'video', view: video_view, video }, [video_view.buffer]);
}

if (audio.frames) {
const audio_view = new Float32Array(memory.buffer, audio.data, audio.frames * 2).slice();
postMessage({ type: 'audio', view: audio_view, sample_rate: audio.rate }, [audio_view.buffer]);
}

this.Unlock();
}

requestAnimationFrame(step);
});
}

/** @returns {Promise<void>} */
async stop() {
this.#stop = true;
await this.#running;

this.Destroy();
}

/** @param {Variable[]} variables @returns {Promise<void>} */
Expand All @@ -234,6 +192,12 @@ export default class Interop {
Cheat.free(this.#instance, cheats_ptr);
}

/** @returns {Promise<void>} */
start() { this.StartGame(); }

/** @returns {Promise<void>} */
stop() { this.Destroy(); }

/** @param {boolean} enable @returns {Promise<void>} */
audio(enable) { this.SetAudio(enable); }

Expand Down
2 changes: 1 addition & 1 deletion ui/sources/services/parallel.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function instrumentContext(context) {
new Uint8Array(sab.buffer).set(encoded_str, 12);
break;
case 'object':
const is_error = result.constructor.name.endsWith('Error');
const is_error = result?.constructor.name.endsWith('Error');
const stringified = JSON.stringify(is_error ? result.stack : result);
const encoded_obj = new TextEncoder().encode(stringified);
if (sab.buffer.byteLength < 12 + encoded_obj.byteLength)
Expand Down

0 comments on commit d32461c

Please sign in to comment.