Improve the way disc images are accessed.

Instead of loading the whole disc image in memory, use the Browser's File API to fetch needed parts of the file.
Has to go through some odd proxying hoops since the File object is owned by the main browser thread.
This commit is contained in:
Jean-Philip Desjardins 2022-01-09 15:02:45 -05:00
parent 0f2c2f47ce
commit 7b5b24c4cf
11 changed files with 199 additions and 49 deletions

View file

@ -479,6 +479,10 @@ if(TARGET_PLATFORM_MACOS OR TARGET_PLATFORM_UNIX OR TARGET_PLATFORM_JS)
set(PLATFORM_SPECIFIC_SRC_FILES Posix_VolumeStream.cpp)
endif()
if(TARGET_PLATFORM_JS)
set(PLATFORM_SPECIFIC_SRC_FILES ${PLATFORM_SPECIFIC_SRC_FILES} Js_DiscImageDeviceStream.cpp Js_DiscImageDeviceStream.h)
endif()
add_library(PlayCore STATIC ${COMMON_SRC_FILES} ${PLATFORM_SPECIFIC_SRC_FILES})
target_link_libraries(PlayCore ${PROJECT_LIBS})
target_include_directories(PlayCore

View file

@ -24,6 +24,9 @@
#include "android/ContentStream.h"
#include "android/ContentUtils.h"
#endif
#ifdef __EMSCRIPTEN__
#include "Js_DiscImageDeviceStream.h"
#endif
#ifdef __APPLE__
#include "TargetConditionals.h"
#endif
@ -58,6 +61,8 @@ static Framework::CStream* CreateImageStream(const fs::path& imagePath)
{
return new Framework::CPosixFileStream(imagePathString.c_str(), O_RDONLY);
}
#elif defined(__EMSCRIPTEN__)
return new CJsDiscImageDeviceStream();
#else
return new Framework::CStdStream(imagePathString.c_str(), "rb");
#endif

View file

@ -0,0 +1,61 @@
#include "Js_DiscImageDeviceStream.h"
#include <stdexcept>
#include <cassert>
#include <emscripten.h>
#include <unistd.h>
void CJsDiscImageDeviceStream::Seek(int64 position, Framework::STREAM_SEEK_DIRECTION whence)
{
switch(whence)
{
case Framework::STREAM_SEEK_SET:
m_position = position;
break;
case Framework::STREAM_SEEK_CUR:
m_position += position;
break;
case Framework::STREAM_SEEK_END:
m_position = MAIN_THREAD_EM_ASM_INT({return Module.discImageDevice.getFileSize()});
break;
}
}
uint64 CJsDiscImageDeviceStream::Tell()
{
return m_position;
}
uint64 CJsDiscImageDeviceStream::Read(void* buffer, uint64 size)
{
if(size == 0) return 0;
assert(size <= std::numeric_limits<uint32>::max());
uint32 positionLow = static_cast<uint32>(m_position);
uint32 positionHigh = static_cast<uint32>(m_position >> 32);
MAIN_THREAD_EM_ASM({
let position = ($1) | ($2 << 32);
Module.discImageDevice.read($0, position, $3);
}, buffer, positionLow, positionHigh, static_cast<uint32>(size));
while(!MAIN_THREAD_EM_ASM_INT({return Module.discImageDevice.isDone()}))
{
usleep(100);
}
m_position += size;
return size;
}
uint64 CJsDiscImageDeviceStream::Write(const void*, uint64)
{
throw std::runtime_error("Not supported.");
}
bool CJsDiscImageDeviceStream::IsEOF()
{
throw std::runtime_error("Not supported.");
}
void CJsDiscImageDeviceStream::Flush()
{
}

View file

@ -0,0 +1,19 @@
#pragma once
#include "Stream.h"
class CJsDiscImageDeviceStream : public Framework::CStream
{
public:
virtual ~CJsDiscImageDeviceStream() = default;
void Seek(int64, Framework::STREAM_SEEK_DIRECTION) override;
uint64 Tell() override;
uint64 Read(void*, uint64) override;
uint64 Write(const void*, uint64) override;
bool IsEOF() override;
void Flush() override;
private:
uint64 m_position = 0;
};

View file

@ -94,6 +94,7 @@ public:
protected:
virtual void CreateVM();
void ResumeImpl();
CMailBox m_mailBox;
@ -108,7 +109,6 @@ private:
void ReloadExecutable(const char*, const CPS2OS::ArgumentList&);
void OnCrtModeChange();
void ResumeImpl();
void PauseImpl();
void DestroyImpl();

View file

@ -2,8 +2,6 @@
#include <emscripten/bind.h>
#include "Ps2VmJs.h"
#include "GSH_OpenGLJs.h"
#include "PS2VM_Preferences.h"
#include "AppConfig.h"
#include "input/PH_GenericInput.h"
#include "InputProviderEmscripten.h"
@ -91,45 +89,20 @@ extern "C" void initVm()
printf("Reset VM\r\n");
}
void loadElf(std::string path)
void bootElf(std::string path)
{
printf("Loading '%s'...\r\n", path.c_str());
try
{
g_virtualMachine->Reset();
g_virtualMachine->m_ee->m_os->BootFromFile(path);
}
catch(const std::exception& ex)
{
printf("Failed to start: %s.\r\n", ex.what());
return;
}
printf("Starting...\r\n");
g_virtualMachine->Resume();
g_virtualMachine->BootElf(path);
}
void bootDiscImage(std::string path)
{
printf("Loading '%s'...\r\n", path.c_str());
try
{
CAppConfig::GetInstance().SetPreferencePath(PREF_PS2_CDROM0_PATH, path);
g_virtualMachine->Reset();
g_virtualMachine->m_ee->m_os->BootFromCDROM();
}
catch(const std::exception& ex)
{
printf("Failed to start: %s.\r\n", ex.what());
return;
}
printf("Starting...\r\n");
g_virtualMachine->Resume();
g_virtualMachine->BootDiscImage(path);
}
EMSCRIPTEN_BINDINGS(Play)
{
using namespace emscripten;
function("loadElf", &loadElf);
function("bootElf", &bootElf);
function("bootDiscImage", &bootDiscImage);
}

View file

@ -2,6 +2,8 @@
#include "Jitter_CodeGen_Wasm.h"
#include "MemoryUtils.h"
#include "BasicBlock.h"
#include "PS2VM_Preferences.h"
#include "AppConfig.h"
extern "C" uint32 LWL_Proxy(uint32, uint32, CMIPS*);
extern "C" uint32 LWR_Proxy(uint32, uint32, CMIPS*);
@ -41,3 +43,44 @@ void CPs2VmJs::CreateVM()
CPS2VM::CreateVM();
}
void CPs2VmJs::BootElf(std::string path)
{
m_mailBox.SendCall([this, path] ()
{
printf("Loading '%s'...\r\n", path.c_str());
try
{
Reset();
m_ee->m_os->BootFromFile(path);
}
catch(const std::exception& ex)
{
printf("Failed to start: %s.\r\n", ex.what());
return;
}
printf("Starting...\r\n");
ResumeImpl();
});
}
void CPs2VmJs::BootDiscImage(std::string path)
{
m_mailBox.SendCall([this, path] ()
{
printf("Loading '%s'...\r\n", path.c_str());
try
{
CAppConfig::GetInstance().SetPreferencePath(PREF_PS2_CDROM0_PATH, path);
Reset();
m_ee->m_os->BootFromCDROM();
}
catch(const std::exception& ex)
{
printf("Failed to start: %s.\r\n", ex.what());
return;
}
printf("Starting...\r\n");
ResumeImpl();
});
}

View file

@ -6,4 +6,7 @@ class CPs2VmJs : public CPS2VM
{
public:
void CreateVM() override;
void BootElf(std::string);
void BootDiscImage(std::string);
};

View file

@ -26,25 +26,27 @@ export const bootFile = createAsyncThunk<void, File>('bootFile',
return;
}
const fileExtension = fileName.substring(fileDotPos);
let url = URL.createObjectURL(file);
let blob = await fetch(url).then(response => {
if(!response.ok) {
return null;
} else {
return response.blob();
}
});
if(blob === null) {
thunkAPI.rejectWithValue(null);
return;
}
let data = new Uint8Array(await blob.arrayBuffer());
let stream = PlayModule.FS.open(fileName, "w+");
PlayModule.FS.write(stream, data, 0, data.length, 0);
PlayModule.FS.close(stream);
if(fileExtension === ".elf") {
PlayModule.loadElf(fileName);
let url = URL.createObjectURL(file);
let blob = await fetch(url).then(response => {
if(!response.ok) {
return null;
} else {
return response.blob();
}
});
if(blob === null) {
thunkAPI.rejectWithValue(null);
return;
}
let data = new Uint8Array(await blob.arrayBuffer());
let stream = PlayModule.FS.open(fileName, "w+");
PlayModule.FS.write(stream, data, 0, data.length, 0);
PlayModule.FS.close(stream);
URL.revokeObjectURL(url);
PlayModule.bootElf(fileName);
} else {
PlayModule.discImageDevice.setFile(file);
PlayModule.bootDiscImage(fileName);
}
}

View file

@ -0,0 +1,38 @@
export default class DiscDevice {
module: any;
doneFlag: Boolean;
file: File | null;
constructor(module: any) {
this.module = module;
this.doneFlag = false;
this.file = null;
}
read(dstPtr: number, offset: number, size: number) {
if(!this.file) {
throw new Error("No file set.");
}
this.doneFlag = false;
let subsection = this.file.slice(offset, offset + size);
subsection.arrayBuffer().then((value: ArrayBuffer) => {
this.module.HEAPU8.set(new Uint8Array(value), dstPtr);
this.doneFlag = true;
});
}
getFileSize() {
if(!this.file) {
throw new Error("No file set.");
}
return this.file.size;
}
isDone() {
return this.doneFlag;
}
setFile(file : File) {
this.file = file;
}
};

View file

@ -1,4 +1,5 @@
import Play from "./Play";
import DiscImageDevice from "./DiscImageDevice";
export let PlayModule : any = null;
@ -14,5 +15,6 @@ export let initPlayModule = async function() {
module_overrides.mainScriptUrlOrBlob = module_overrides.locateFile('Play.js');
PlayModule = await Play(module_overrides);
PlayModule.FS.mkdir("/work");
PlayModule.discImageDevice = new DiscImageDevice(PlayModule);
PlayModule.ccall("initVm", "", [], []);
};