mirror of
https://github.com/jpd002/Play-.git
synced 2025-04-28 13:47:57 +03:00
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:
parent
0f2c2f47ce
commit
7b5b24c4cf
11 changed files with 199 additions and 49 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
61
Source/Js_DiscImageDeviceStream.cpp
Normal file
61
Source/Js_DiscImageDeviceStream.cpp
Normal 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()
|
||||
{
|
||||
}
|
19
Source/Js_DiscImageDeviceStream.h
Normal file
19
Source/Js_DiscImageDeviceStream.h
Normal 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;
|
||||
};
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
|
@ -6,4 +6,7 @@ class CPs2VmJs : public CPS2VM
|
|||
{
|
||||
public:
|
||||
void CreateVM() override;
|
||||
|
||||
void BootElf(std::string);
|
||||
void BootDiscImage(std::string);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
38
js/play_browser/src/DiscImageDevice.ts
Normal file
38
js/play_browser/src/DiscImageDevice.ts
Normal 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;
|
||||
}
|
||||
};
|
|
@ -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", "", [], []);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue