mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-06-28 20:35:22 +02:00
emscripten friendly sdl loop
This commit is contained in:
parent
6247795f12
commit
9da56ebaba
6 changed files with 155 additions and 146 deletions
|
|
@ -258,6 +258,8 @@ If running under Firefox and you hit "out of memory" on dev console, close the e
|
||||||
|
|
||||||
To run the binary (after building) you should be fine with `node ./eden-cli.js`. For obvious reasons no Qt frontend is available on WASM, support for Vulkan is done charily via [llvmpipe2wasm](https://github.com/Devsh-Graphics-Programming/llvmpipe2wasm).
|
To run the binary (after building) you should be fine with `node ./eden-cli.js`. For obvious reasons no Qt frontend is available on WASM, support for Vulkan is done charily via [llvmpipe2wasm](https://github.com/Devsh-Graphics-Programming/llvmpipe2wasm).
|
||||||
|
|
||||||
|
If you run into the error "acorn.js can't be found" check [this associated issue](https://github.com/emscripten-core/emscripten/issues/13368), the fix in short is `npm --global install acorn`. On FreeBSD you could run `npm` under root, or you could do the sane thing and do `sudo chown -R $USER /usr/local/lib/node_modules/ /usr/local/bin/` (remember to restore permissions afterwards!) unless you wish to run `npm` under root which is generally a bad idea.
|
||||||
|
|
||||||
2026-06-09: As of writing, no Dynarmic-based JIT is possible on this target, full interpreted emulation is the only reasonable option. While there is some efforts on making a JIT like [here](https://github.com/wingo/wasm-jit) or [here](https://wingolog.org/archives/2022/08/18/just-in-time-code-generation-within-webassembly), the result is so latency expensive we're better off using an interpreter instead.
|
2026-06-09: As of writing, no Dynarmic-based JIT is possible on this target, full interpreted emulation is the only reasonable option. While there is some efforts on making a JIT like [here](https://github.com/wingo/wasm-jit) or [here](https://wingolog.org/archives/2022/08/18/just-in-time-code-generation-within-webassembly), the result is so latency expensive we're better off using an interpreter instead.
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|
|
||||||
|
|
@ -77,12 +77,13 @@ if (NOT MSVC)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (PLATFORM_EMSCRIPTEN)
|
if (PLATFORM_EMSCRIPTEN)
|
||||||
# 10GB is required... yikes!
|
# 10GB is required at max... yikes!
|
||||||
target_link_options(yuzu-cmd PRIVATE
|
target_link_options(yuzu-cmd PRIVATE
|
||||||
-sALLOW_MEMORY_GROWTH=1
|
-sALLOW_MEMORY_GROWTH=1
|
||||||
|
-sINITIAL_MEMORY=33554432
|
||||||
-sMAXIMUM_MEMORY=10737418240
|
-sMAXIMUM_MEMORY=10737418240
|
||||||
-sGLOBAL_BASE=16777216
|
-sGLOBAL_BASE=16777216
|
||||||
-sEXPORTED_RUNTIME_METHODS="['FS']"
|
-sEXPORTED_RUNTIME_METHODS=['FS']
|
||||||
-sPTHREAD_POOL_SIZE_STRICT=0
|
-sPTHREAD_POOL_SIZE_STRICT=0
|
||||||
-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency)
|
-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,6 @@ EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Co
|
||||||
}
|
}
|
||||||
|
|
||||||
EmuWindow_SDL3::~EmuWindow_SDL3() {
|
EmuWindow_SDL3::~EmuWindow_SDL3() {
|
||||||
#ifdef __EMSCRIPTEN__
|
|
||||||
emscripten_cancel_main_loop();
|
|
||||||
#endif
|
|
||||||
system.HIDCore().UnloadInputDevices();
|
system.HIDCore().UnloadInputDevices();
|
||||||
input_subsystem->Shutdown();
|
input_subsystem->Shutdown();
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
|
|
@ -169,23 +166,8 @@ void EmuWindow_SDL3::Fullscreen() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuWindow_SDL3::WaitEvent() {
|
void EmuWindow_SDL3::OnEvent(SDL_Event& event) {
|
||||||
// Called on main thread
|
// Called on main thread
|
||||||
SDL_Event event;
|
|
||||||
|
|
||||||
if (!SDL_WaitEvent(&event)) {
|
|
||||||
const char* error = SDL_GetError();
|
|
||||||
if (!error || strcmp(error, "") == 0) {
|
|
||||||
// https://github.com/libsdl-org/SDL/issues/5780
|
|
||||||
// Sometimes SDL will return without actually having hit an error condition;
|
|
||||||
// just ignore it in this case.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_CRITICAL(Frontend, "SDL_WaitEvent failed: {}", error);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case SDL_EVENT_WINDOW_RESIZED:
|
case SDL_EVENT_WINDOW_RESIZED:
|
||||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||||
|
|
@ -255,6 +237,9 @@ void EmuWindow_SDL3::WaitEvent() {
|
||||||
|
|
||||||
// Credits to Samantas5855 and others for this function.
|
// Credits to Samantas5855 and others for this function.
|
||||||
void EmuWindow_SDL3::SetWindowIcon() {
|
void EmuWindow_SDL3::SetWindowIcon() {
|
||||||
|
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
|
||||||
|
// Icons do not work yet
|
||||||
|
#else
|
||||||
SDL_IOStream* const yuzu_icon_stream = SDL_IOFromConstMem((void*)yuzu_icon, yuzu_icon_size);
|
SDL_IOStream* const yuzu_icon_stream = SDL_IOFromConstMem((void*)yuzu_icon, yuzu_icon_size);
|
||||||
if (yuzu_icon_stream == nullptr) {
|
if (yuzu_icon_stream == nullptr) {
|
||||||
LOG_WARNING(Frontend, "Failed to create Eden icon stream.");
|
LOG_WARNING(Frontend, "Failed to create Eden icon stream.");
|
||||||
|
|
@ -268,6 +253,7 @@ void EmuWindow_SDL3::SetWindowIcon() {
|
||||||
// The icon is attached to the window pointer
|
// The icon is attached to the window pointer
|
||||||
SDL_SetWindowIcon(render_window, window_icon);
|
SDL_SetWindowIcon(render_window, window_icon);
|
||||||
SDL_DestroySurface(window_icon);
|
SDL_DestroySurface(window_icon);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuWindow_SDL3::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
|
void EmuWindow_SDL3::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include "core/frontend/graphics_context.h"
|
#include "core/frontend/graphics_context.h"
|
||||||
|
|
||||||
struct SDL_Window;
|
struct SDL_Window;
|
||||||
|
union SDL_Event;
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
|
|
@ -35,7 +36,7 @@ public:
|
||||||
bool IsShown() const override;
|
bool IsShown() const override;
|
||||||
|
|
||||||
/// Wait for the next event on the main thread.
|
/// Wait for the next event on the main thread.
|
||||||
void WaitEvent();
|
void OnEvent(SDL_Event& event);
|
||||||
|
|
||||||
// Sets the window icon from yuzu.bmp
|
// Sets the window icon from yuzu.bmp
|
||||||
void SetWindowIcon();
|
void SetWindowIcon();
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
#endif
|
#endif
|
||||||
|
#define SDL_MAIN_USE_CALLBACKS 1
|
||||||
|
#include <SDL3/SDL_main.h>
|
||||||
|
|
||||||
#include <fmt/ostream.h>
|
#include <fmt/ostream.h>
|
||||||
|
|
||||||
|
|
@ -43,9 +45,7 @@
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// windows.h needs to be included before shellapi.h
|
// windows.h needs to be included before shellapi.h
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
|
|
||||||
#include "common/windows/timer_resolution.h"
|
#include "common/windows/timer_resolution.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -178,8 +178,15 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) {
|
||||||
std::cout << std::endl << "* " << message << std::endl << std::endl;
|
std::cout << std::endl << "* " << message << std::endl << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Application entry point
|
struct SdlState {
|
||||||
int main(int argc, char** argv) {
|
Common::DetachedTasks detached_tasks{};
|
||||||
|
Core::System system{};
|
||||||
|
std::unique_ptr<EmuWindow_SDL3> emu_window;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
||||||
|
SdlState* state = new SdlState();
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||||
freopen("CONOUT$", "wb", stdout);
|
freopen("CONOUT$", "wb", stdout);
|
||||||
|
|
@ -190,16 +197,14 @@ int main(int argc, char** argv) {
|
||||||
Common::Log::Initialize();
|
Common::Log::Initialize();
|
||||||
Common::Log::SetColorConsoleBackendEnabled(true);
|
Common::Log::SetColorConsoleBackendEnabled(true);
|
||||||
Common::Log::Start();
|
Common::Log::Start();
|
||||||
Common::DetachedTasks detached_tasks;
|
|
||||||
|
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
int argc_w;
|
int argc_w;
|
||||||
auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
|
auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
|
||||||
|
|
||||||
if (argv_w == nullptr) {
|
if (argv_w == nullptr) {
|
||||||
LOG_CRITICAL(Frontend, "Failed to get command line arguments");
|
LOG_CRITICAL(Frontend, "Failed to get command line arguments");
|
||||||
return -1;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
std::string filepath;
|
std::string filepath;
|
||||||
|
|
@ -247,7 +252,7 @@ int main(int argc, char** argv) {
|
||||||
break;
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
PrintHelp(argv[0]);
|
PrintHelp(argv[0]);
|
||||||
return 0;
|
return SDL_APP_FAILURE;
|
||||||
case 'g':
|
case 'g':
|
||||||
filepath = std::string(optarg);
|
filepath = std::string(optarg);
|
||||||
break;
|
break;
|
||||||
|
|
@ -264,7 +269,7 @@ int main(int argc, char** argv) {
|
||||||
if (!std::regex_match(str_arg, re)) {
|
if (!std::regex_match(str_arg, re)) {
|
||||||
std::cout << "Wrong format for option --multiplayer\n";
|
std::cout << "Wrong format for option --multiplayer\n";
|
||||||
PrintHelp(argv[0]);
|
PrintHelp(argv[0]);
|
||||||
return 0;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::smatch match;
|
std::smatch match;
|
||||||
|
|
@ -274,17 +279,16 @@ int main(int argc, char** argv) {
|
||||||
password = match[2];
|
password = match[2];
|
||||||
address = match[3];
|
address = match[3];
|
||||||
if (!match[4].str().empty()) {
|
if (!match[4].str().empty()) {
|
||||||
port = static_cast<u16>(std::strtoul(match[4].str().c_str(), nullptr, 0));
|
port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0));
|
||||||
}
|
}
|
||||||
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
|
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
|
||||||
if (!std::regex_match(nickname, nickname_re)) {
|
if (!std::regex_match(nickname, nickname_re)) {
|
||||||
std::cout
|
LOG_ERROR(Frontend, "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
|
||||||
<< "Nickname is not valid. Must be 4 to 20 alphanumeric characters.\n";
|
return SDL_APP_FAILURE;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
if (address.empty()) {
|
if (address.empty()) {
|
||||||
std::cout << "Address to room must not be empty.\n";
|
LOG_ERROR(Frontend, "Address to room must not be empty");
|
||||||
return 0;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -297,7 +301,7 @@ int main(int argc, char** argv) {
|
||||||
break;
|
break;
|
||||||
case 'v':
|
case 'v':
|
||||||
PrintVersion();
|
PrintVersion();
|
||||||
return 0;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
@ -341,79 +345,73 @@ int main(int argc, char** argv) {
|
||||||
|
|
||||||
if (filepath.empty()) {
|
if (filepath.empty()) {
|
||||||
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
|
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
|
||||||
return -1;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::System system{};
|
state->system.Initialize();
|
||||||
system.Initialize();
|
|
||||||
|
|
||||||
InputCommon::InputSubsystem input_subsystem{};
|
InputCommon::InputSubsystem input_subsystem{};
|
||||||
|
|
||||||
// Apply the command line arguments
|
// Apply the command line arguments
|
||||||
system.ApplySettings();
|
state->system.ApplySettings();
|
||||||
|
|
||||||
std::unique_ptr<EmuWindow_SDL3> emu_window;
|
|
||||||
switch (Settings::values.renderer_backend.GetValue()) {
|
switch (Settings::values.renderer_backend.GetValue()) {
|
||||||
#ifdef HAS_OPENGL
|
#ifdef HAS_OPENGL
|
||||||
case Settings::RendererBackend::OpenGL_GLSL:
|
case Settings::RendererBackend::OpenGL_GLSL:
|
||||||
case Settings::RendererBackend::OpenGL_GLASM:
|
case Settings::RendererBackend::OpenGL_GLASM:
|
||||||
case Settings::RendererBackend::OpenGL_SPIRV:
|
case Settings::RendererBackend::OpenGL_SPIRV:
|
||||||
emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, system, fullscreen);
|
state->emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, state->system, fullscreen);
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
case Settings::RendererBackend::Vulkan:
|
case Settings::RendererBackend::Vulkan:
|
||||||
emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, system, fullscreen);
|
state->emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, state->system, fullscreen);
|
||||||
break;
|
break;
|
||||||
case Settings::RendererBackend::Null:
|
case Settings::RendererBackend::Null:
|
||||||
emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, system, fullscreen);
|
state->emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, state->system, fullscreen);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOG_CRITICAL(Frontend, "Invalid renderer backend");
|
LOG_CRITICAL(Frontend, "Invalid renderer backend");
|
||||||
return -1;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
Common::Windows::SetCurrentTimerResolutionToMaximum();
|
Common::Windows::SetCurrentTimerResolutionToMaximum();
|
||||||
system.CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution());
|
state->system.CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
state->system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||||
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
|
state->system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
|
||||||
system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
|
state->system.GetFileSystemController().CreateFactories(*state->system.GetFilesystem());
|
||||||
system.GetUserChannel().clear();
|
state->system.GetUserChannel().clear();
|
||||||
|
|
||||||
Service::AM::FrontendAppletParameters load_parameters{
|
Service::AM::FrontendAppletParameters load_parameters{
|
||||||
.applet_id = Service::AM::AppletId::Application,
|
.applet_id = Service::AM::AppletId::Application,
|
||||||
};
|
};
|
||||||
const Core::SystemResultStatus load_result{system.Load(*emu_window, filepath, load_parameters)};
|
const Core::SystemResultStatus load_result = state->system.Load(*state->emu_window, filepath, load_parameters);
|
||||||
|
|
||||||
switch (load_result) {
|
switch (load_result) {
|
||||||
case Core::SystemResultStatus::ErrorGetLoader:
|
|
||||||
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath);
|
|
||||||
return -1;
|
|
||||||
case Core::SystemResultStatus::ErrorLoader:
|
|
||||||
LOG_CRITICAL(Frontend, "Failed to load ROM!");
|
|
||||||
return -1;
|
|
||||||
case Core::SystemResultStatus::ErrorNotInitialized:
|
|
||||||
LOG_CRITICAL(Frontend, "CPUCore not initialized");
|
|
||||||
return -1;
|
|
||||||
case Core::SystemResultStatus::ErrorVideoCore:
|
|
||||||
LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!");
|
|
||||||
return -1;
|
|
||||||
case Core::SystemResultStatus::Success:
|
case Core::SystemResultStatus::Success:
|
||||||
break; // Expected case
|
break; // Expected case
|
||||||
|
case Core::SystemResultStatus::ErrorGetLoader:
|
||||||
|
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath);
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
case Core::SystemResultStatus::ErrorLoader:
|
||||||
|
LOG_CRITICAL(Frontend, "Failed to load ROM!");
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
case Core::SystemResultStatus::ErrorNotInitialized:
|
||||||
|
LOG_CRITICAL(Frontend, "CPUCore not initialized");
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
case Core::SystemResultStatus::ErrorVideoCore:
|
||||||
|
LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!");
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
default:
|
default:
|
||||||
if (static_cast<u32>(load_result) >
|
const u16 loader_id = u16(Core::SystemResultStatus::ErrorLoader);
|
||||||
static_cast<u32>(Core::SystemResultStatus::ErrorLoader)) {
|
const u16 error_id = u16(load_result) - loader_id;
|
||||||
const u16 loader_id = static_cast<u16>(Core::SystemResultStatus::ErrorLoader);
|
LOG_CRITICAL(Frontend,
|
||||||
const u16 error_id = static_cast<u16>(load_result) - loader_id;
|
"While attempting to load the ROM requested, an error occurred. Please "
|
||||||
LOG_CRITICAL(Frontend,
|
"refer to the Eden wiki for more information or the Eden discord for "
|
||||||
"While attempting to load the ROM requested, an error occurred. Please "
|
"additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
|
||||||
"refer to the Eden wiki for more information or the Eden discord for "
|
loader_id, error_id, Loader::ResultStatus(error_id));
|
||||||
"additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
|
return SDL_APP_FAILURE;
|
||||||
loader_id, error_id, static_cast<Loader::ResultStatus>(error_id));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (use_multiplayer) {
|
if (use_multiplayer) {
|
||||||
|
|
@ -422,49 +420,47 @@ int main(int argc, char** argv) {
|
||||||
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
|
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
|
||||||
member->BindOnStateChanged(OnStateChanged);
|
member->BindOnStateChanged(OnStateChanged);
|
||||||
member->BindOnError(OnNetworkError);
|
member->BindOnError(OnNetworkError);
|
||||||
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port,
|
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, nickname);
|
||||||
nickname);
|
|
||||||
member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password);
|
member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password);
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(Network, "Could not access RoomMember");
|
LOG_ERROR(Network, "Could not access RoomMember");
|
||||||
return 0;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Core is loaded, start the GPU (makes the GPU contexts current to this thread)
|
// Core is loaded, start the GPU (makes the GPU contexts current to this thread)
|
||||||
system.GPU().Start();
|
state->system.GPU().Start();
|
||||||
system.GetCpuManager().OnGpuReady();
|
state->system.GetCpuManager().OnGpuReady();
|
||||||
|
|
||||||
if (Settings::values.use_disk_shader_cache.GetValue()) {
|
if (Settings::values.use_disk_shader_cache.GetValue()) {
|
||||||
system.Renderer().ReadRasterizer()->LoadDiskResources(
|
state->system.Renderer().ReadRasterizer()->LoadDiskResources(
|
||||||
system.GetApplicationProcessProgramID(), std::stop_token{},
|
state->system.GetApplicationProcessProgramID(), std::stop_token{},
|
||||||
[](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
|
[](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
|
||||||
}
|
}
|
||||||
|
|
||||||
system.RegisterExitCallback([&] {
|
// don't do anything, SDL3 already exists for us :D
|
||||||
// Just exit right away.
|
state->system.RegisterExitCallback([] {});
|
||||||
exit(0);
|
void(state->system.Run());
|
||||||
});
|
if (state->system.DebuggerEnabled())
|
||||||
void(system.Run());
|
state->system.InitializeDebugger();
|
||||||
if (system.DebuggerEnabled()) {
|
return SDL_APP_SUCCESS;
|
||||||
system.InitializeDebugger();
|
}
|
||||||
}
|
extern "C" SDL_AppResult SDL_AppIterate(void *appstate) {
|
||||||
|
SdlState *state = (SdlState *)appstate;
|
||||||
#ifdef __EMSCRIPTEN__
|
return state->emu_window->IsOpen() ? SDL_APP_CONTINUE : SDL_APP_SUCCESS;
|
||||||
// Required so lambda fits snuggly into our "main loop"
|
}
|
||||||
static EmuWindow_SDL3* static_ems_emu_window = emu_window.get();
|
extern "C" SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
|
||||||
emscripten_set_main_loop([]() {
|
SdlState *state = (SdlState *)appstate;
|
||||||
static_ems_emu_window->WaitEvent();
|
state->emu_window->OnEvent(*event);
|
||||||
}, 0, 1);
|
return SDL_APP_SUCCESS;
|
||||||
#else
|
}
|
||||||
while (emu_window->IsOpen())
|
extern "C" void SDL_AppQuit(void *appstate, SDL_AppResult result) {
|
||||||
emu_window->WaitEvent();
|
SdlState *state = (SdlState *)appstate;
|
||||||
#endif
|
state->system.DetachDebugger();
|
||||||
system.DetachDebugger();
|
void(state->system.Pause());
|
||||||
void(system.Pause());
|
state->system.ShutdownMainProcess();
|
||||||
system.ShutdownMainProcess();
|
state->detached_tasks.WaitForAllTasks();
|
||||||
detached_tasks.WaitForAllTasks();
|
delete state;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define VMA_IMPLEMENTATION
|
#define VMA_IMPLEMENTATION
|
||||||
|
|
|
||||||
99
tools/miniserver.js
Normal file → Executable file
99
tools/miniserver.js
Normal file → Executable file
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/local/env node
|
#!/usr/bin/env node
|
||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
@ -21,51 +21,61 @@ const server = createServer((req, res) => {
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>eden-cli</title>
|
<title>eden-cli</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin:0;padding:0;background-color:black;font-family:Monospace,Tahoma,Arial;">
|
||||||
<script>
|
<script>
|
||||||
var Module = {}; // MUST be global otherwise wasm does wawawawa
|
var Module = { //do not prepend var
|
||||||
function finished_body() {
|
mainScriptUrlOrBlob: 'eden-cli.js',
|
||||||
Module = { //do not prepend var
|
print: (e) => {
|
||||||
print: (e) => {
|
e = e.replace('[1;31m', '<span style="color:red;font-weight:bold;">');
|
||||||
e = e.replace('[1;31m', '<span style="color:red;font-weight:bold;">');
|
e = e.replace('[0;37m', '<span style="color:white;font-weight:bold;">');
|
||||||
e = e.replace('[0;37m', '<span style="color:white;font-weight:bold;">');
|
e = e.replace('[1;35m', '<span style="color:pink;font-weight:bold;">');
|
||||||
e = e.replace('[1;35m', '<span style="color:pink;font-weight:bold;">');
|
e = e.replace('[1;33m', '<span style="color:yellow;font-weight:bold;">');
|
||||||
e = e.replace('[1;33m', '<span style="color:yellow;font-weight:bold;">');
|
e = e.replace('[0m', '</span>');
|
||||||
e = e.replace('[0m', '</span>');
|
tty_stdout.innerHTML += \`\${e}</br>\`;
|
||||||
tty_stdout.innerHTML += e + '</br>';
|
},
|
||||||
},
|
printErr: (e) => {
|
||||||
printErr: (e) => {
|
tty_stdout.innerHTML += \`<span style="color:red">\${e}</span></br>\`;
|
||||||
tty_stdout.innerHTML += '<span style="color:red">' + e + '</span></br>';
|
},
|
||||||
},
|
// not a wasm func but idc
|
||||||
// not a wasm func but idc
|
printInternal: (e) => {
|
||||||
printInternal: (e) => {
|
tty_stdout.innerHTML += \`<span style="color:pink">Internal WASM: \${e}</span></br>\`;
|
||||||
tty_stdout.innerHTML += '<span style="color:pink">Internal WASM: ' + e + '</span></br>';
|
},
|
||||||
},
|
onRuntimeInitialized: () => { Module.printInternal("runtime ok"); },
|
||||||
onRuntimeInitialized: () => { Module.printInternal("runtime ok"); },
|
setStatus: (e) => { Module.printInternal(e); },
|
||||||
setStatus: (e) => { Module.printInternal(e); },
|
monitorRunDependencies: (e) => { Module.printInternal("monitor deps: " + e); },
|
||||||
monitorRunDependencies: (e) => { Module.printInternal("monitor deps: " + e); },
|
__wasm_call_ctors: () => { Module.printInternal("ctors beep"); },
|
||||||
__wasm_call_ctors: () => { Module.printInternal("ctors beep"); },
|
};
|
||||||
};
|
var tty_stdout = document.createElement('div');
|
||||||
|
document.body.appendChild(tty_stdout);
|
||||||
|
Module.arguments = ['game.nro'];
|
||||||
|
|
||||||
var tty_stdout = document.createElement('div');
|
var gameNroFileBuffer = {};
|
||||||
document.body.appendChild(tty_stdout);
|
|
||||||
Module.printInternal("loading from ${build_dir}/eden-cli.js");
|
|
||||||
|
|
||||||
Module.arguments = ['game.nro'];
|
Module.printInternal("Atomics: " + window.Atomics);
|
||||||
|
Module.printInternal("SharedArrayBuffer: " + window.SharedArrayBuffer);
|
||||||
|
Module.printInternal("trying to load script (if it hangs here check console)");
|
||||||
|
|
||||||
|
fetch('game.nro').then((resp) => {
|
||||||
|
if (!resp.ok)
|
||||||
|
throw Error(\`\${resp.status}\`);
|
||||||
|
return resp.arrayBuffer();
|
||||||
|
}).then((buffer) => {
|
||||||
|
gameNroFileBuffer = buffer;
|
||||||
|
Module.printInternal("buffer: " + gameNroFileBuffer);
|
||||||
|
|
||||||
|
// load the thingy AFTER loading the nro
|
||||||
|
Module.printInternal(\`loading from ${build_dir}\${Module.mainScriptUrlOrBlob}\`);
|
||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = '/eden-cli.js';
|
script.src = '/eden-cli.js';
|
||||||
script.onload = () => {
|
script.onload = () => {
|
||||||
Module.printInternal("Atomics: " + window.Atomics);
|
// copy just most relevant :)
|
||||||
Module.printInternal("SharedArrayBuffer: " + window.SharedArrayBuffer);
|
console.log(Module);
|
||||||
Module.printInternal("trying to load script (if it hangs here check console)");
|
Module.FS.writeFile('/game.nro', new DataView(gameNroFileBuffer));
|
||||||
|
|
||||||
Module.FS.writeFile('/game.nro', [1,2,3]);
|
|
||||||
};
|
};
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
}
|
}).catch(Module.printErr);
|
||||||
</script>
|
</script>
|
||||||
</head>
|
|
||||||
<body style="margin:0;padding:0;background-color:black;font-family:Monospace,Tahoma,Arial;" onload="finished_body();">
|
|
||||||
</body>
|
</body>
|
||||||
</html>`);
|
</html>`);
|
||||||
} else if (req.url === '/eden-cli.js') {
|
} else if (req.url === '/eden-cli.js') {
|
||||||
|
|
@ -86,12 +96,25 @@ function finished_body() {
|
||||||
});
|
});
|
||||||
res.end(content, 'utf-8');
|
res.end(content, 'utf-8');
|
||||||
});
|
});
|
||||||
|
} else if (req.url === '/game.nro') {
|
||||||
|
readFile(nro_file, (err, content) => {
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Type': 'application/wasm',
|
||||||
|
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||||
|
'Cross-Origin-Embedder-Policy': 'require-corp'
|
||||||
|
});
|
||||||
|
res.end(content, 'utf-8');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.writeHead(404, {});
|
||||||
|
res.end('', 'utf-8');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const build_dir = process.argv[2];
|
const build_dir = process.argv[2];
|
||||||
if (build_dir === undefined) {
|
const nro_file = process.argv[3];
|
||||||
console.log(`Usage: ${process.argv[0]} ${process.argv[1]} [build directory]`);
|
if (typeof build_dir == "undefined" || typeof nro_file == "undefined") {
|
||||||
|
console.log(`Usage: ${process.argv[0]} ${process.argv[1]} [build directory] [NRO file]`);
|
||||||
} else {
|
} else {
|
||||||
server.listen(2210, () => {
|
server.listen(2210, () => {
|
||||||
console.log(`${process.argv[0]} ${process.argv[1]} http://localhost:2210`);
|
console.log(`${process.argv[0]} ${process.argv[1]} http://localhost:2210`);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue