Skip to content

Commit 4091182

Browse files
committed
Workerized (non-async) web player, using OPFS
This patch eliminates the need for asyncify and uses modern filesystem APIs instead of the deprecated, unmaintained BrowserFS. This is a WIP patch because it won't fully work until these two Emscripten PRs land and are released: emscripten-core/emscripten#23518 emscripten-core/emscripten#23021 The former fixes an offscreen canvas context recreation bug, and the latter adds an equivalent to BrowserFS's XHR filesystem (but without the hazardous running-XHR-on-the-main-thread problem). The biggest issue is that local storage of users who were using the old version of the webplayer will be gone when they switch to the new webplayer. I don't have a good story for converting the old BrowserFS IDBFS contents into the new OPFS filesystem (the move is worth doing because OPFS supports seeking and reading only bits of a file, and because BrowserFS is dead). I've kept around the old libretro webplayer under pkg/emscripten/libretro-classic, and with these make flags you can build a non-workerized RA that uses asyncify to sleep as before: make -f Makefile.emscripten libretro=$CORE HAVE_WORKER=0 HAVE_WASMFS=0 PTHREAD=0 HAVE_AL=1 I also moved the default directory for core content on emscripten to not be a subdirectory of the local filesystem mount, because it's confusing to have a subdirectory that's lazily fetched and not mirrored to the local storage. I think it won't impact existing users of the classic web player because they already have a retroarch.cfg in place.
1 parent a761596 commit 4091182

File tree

10 files changed

+868
-144
lines changed

10 files changed

+868
-144
lines changed

Makefile.emscripten

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ ifeq ($(HAVE_SDL2), 1)
8787
endif
8888

8989
ifeq ($(HAVE_WASMFS), 1)
90-
LIBS += -s WASMFS -s FORCE_FILESYSTEM=1
90+
LIBS += -s WASMFS -s FORCE_FILESYSTEM=1 -lfetchfs.js -lopfs.js
91+
EXPORTS = 'FS', 'FETCHFS', 'OPFS'
92+
else
93+
EXPORTS = 'FS'
9194
endif
9295

9396
ifeq ($(HAVE_WORKER), 1)
@@ -99,7 +102,7 @@ else
99102
endif
100103

101104
LDFLAGS := -L. --no-heap-copy $(LIBS) -s TOTAL_MEMORY=$(MEMORY) -s NO_EXIT_RUNTIME=0 -s FULL_ES2=1 \
102-
-s "EXPORTED_RUNTIME_METHODS=['callMain', 'FS', 'PATH', 'ERRNO_CODES']" \
105+
-s "EXPORTED_RUNTIME_METHODS=[$(EXPORTS), 'callMain', 'FS', 'PATH', 'ERRNO_CODES']" \
103106
-s ALLOW_MEMORY_GROWTH=1 -s "EXPORTED_FUNCTIONS=['_main', '_malloc', '_cmd_savefiles', '_cmd_save_state', '_cmd_load_state', '_cmd_take_screenshot']" \
104107
-s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME="libretro_$(subst -,_,$(LIBRETRO))" \
105108
-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0 \

frontend/drivers/platform_emscripten.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ static void frontend_emscripten_get_env(int *argc, char *argv[],
120120
"config", sizeof(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG]));
121121
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_MENU_CONTENT], user_path,
122122
"content", sizeof(g_defaults.dirs[DEFAULT_DIR_MENU_CONTENT]));
123-
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS], user_path,
124-
"content/downloads", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS]));
123+
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS], base_path,
124+
"downloads", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS]));
125125
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_PLAYLIST], user_path,
126126
"playlists", sizeof(g_defaults.dirs[DEFAULT_DIR_PLAYLIST]));
127127
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_REMAP], g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG],
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>RetroArch Web Player</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<!-- Bootstrap core CSS -->
8+
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/css/bootstrap.min.css" rel="stylesheet" type="text/css">
9+
<!-- Font Awesome -->
10+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.0/css/font-awesome.min.css">
11+
<!-- Material Design Bootstrap -->
12+
<link href="//cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.1.1/css/mdb.min.css" rel="stylesheet">
13+
14+
<link href="libretro.css" rel="stylesheet" type="text/css">
15+
<link rel="shortcut icon" href="media/retroarch.ico" />
16+
17+
</head>
18+
<body>
19+
<!--Navbar-->
20+
<nav class="navbar navbar-dark bg-primary">
21+
<div class="container">
22+
<!--navbar content-->
23+
<div class="navbar-toggleable-xs">
24+
<!--Links-->
25+
<ul class="nav navbar-nav">
26+
<div class="dropdown">
27+
<li class="nav-item dropdown">
28+
<button class="btn btn-primary dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Core Selection</button>
29+
<div class="dropdown-menu dropdown-primary" aria-labelledby="dropdownMenu1" data-dropdown-in="fadeIn" data-dropdown-out="fadeOut" id="core-selector">
30+
<a class="dropdown-item" href="." data-core="2048">2048</a>
31+
<a class="dropdown-item" href="." data-core="arduous">Arduous</a>
32+
<a class="dropdown-item" href="." data-core="bk">BK</a>
33+
<a class="dropdown-item" href="." data-core="bluemsx">BlueMSX</a>
34+
<a class="dropdown-item" href="." data-core="chailove">ChaiLove</a>
35+
<a class="dropdown-item" href="." data-core="craft">Craft</a>
36+
<a class="dropdown-item" href="." data-core="desmume">DeSmuME</a>
37+
<a class="dropdown-item" href="." data-core="dosbox">DOSBox</a>
38+
<a class="dropdown-item" href="." data-core="easyrpg">EasyRPG</a>
39+
<a class="dropdown-item" href="." data-core="ecwolf">ECWolf</a>
40+
<a class="dropdown-item" href="." data-core="fbalpha2012">FB Alpha 2012</a>
41+
<a class="dropdown-item" href="." data-core="fbalpha2012_cps1">FB Alpha 2012 CPS1</a>
42+
<a class="dropdown-item" href="." data-core="fbalpha2012_cps2">FB Alpha 2012 CPS2</a>
43+
<a class="dropdown-item" href="." data-core="fbalpha2012_neo">FB Alpha 2012 NeoGeo</a>
44+
<a class="dropdown-item" href="." data-core="fceumm">FCEUmm</a>
45+
<a class="dropdown-item" href="." data-core="ffmpeg">FFmpeg</a>
46+
<a class="dropdown-item" href="." data-core="freechaf">FreeChaF</a>
47+
<a class="dropdown-item" href="." data-core="gambatte">Gambatte</a>
48+
<a class="dropdown-item" href="." data-core="gme">Game Music Emu</a>
49+
<a class="dropdown-item" href="." data-core="gearboy">GearBoy</a>
50+
<a class="dropdown-item" href="." data-core="gearcoleco">GearColeco</a>
51+
<a class="dropdown-item" href="." data-core="gearsystem">GearSystem</a>
52+
<a class="dropdown-item" href="." data-core="genesis_plus_gx">Genesis Plus GX</a>
53+
<a class="dropdown-item" href="." data-core="genesis_plus_gx_wide">Genesis Plus GX Wide</a>
54+
<a class="dropdown-item" href="." data-core="glupen64">GLupeN64</a>
55+
<!--<a class="dropdown-item" href="." data-core="gpsp">gPSP</a>-->
56+
<a class="dropdown-item" href="." data-core="handy">Handy</a>
57+
<a class="dropdown-item" href="." data-core="jaxe">JAXE</a>
58+
<a class="dropdown-item" href="." data-core="jumpnbump">Jump 'n Bump</a>
59+
<a class="dropdown-item" href="." data-core="lowresnx">LowResNX</a>
60+
<a class="dropdown-item" href="." data-core="lutro">Lutro</a>
61+
<a class="dropdown-item" href="." data-core="m2000">M2000</a>
62+
<a class="dropdown-item" href="." data-core="mame2000">MAME 2000</a>
63+
<a class="dropdown-item" href="." data-core="mame2003">MAME 2003</a>
64+
<a class="dropdown-item" href="." data-core="mame2003_plus">MAME 2003-Plus</a>
65+
<a class="dropdown-item" href="." data-core="mednafen_lynx">Mednafen Lynx</a>
66+
<a class="dropdown-item" href="." data-core="mednafen_ngp">Mednafen Neo Geo Pocket</a>
67+
<a class="dropdown-item" href="." data-core="mednafen_pce_fast">Mednafen PC Engine Fast</a>
68+
<!--<a class="dropdown-item" href="." data-core="mednafen_pcfx">Mednafen/Beetle PCFX</a>-->
69+
<a class="dropdown-item" href="." data-core="mednafen_psx">Mednafen/Beetle PSX</a>
70+
<!--<a class="dropdown-item" href="." data-core="mednafen_saturn">Mednafen/Beetle Saturn</a>-->
71+
<a class="dropdown-item" href="." data-core="mednafen_snes">Mednafen/Beetle SNES</a>
72+
<a class="dropdown-item" href="." data-core="mednafen_vb">Mednafen/Beetle Virtual Boy</a>
73+
<a class="dropdown-item" href="." data-core="mednafen_wswan">Mednafen/Beetle WonderSwan</a>
74+
<a class="dropdown-item" href="." data-core="mgba">Mgba</a>
75+
<a class="dropdown-item" href="." data-core="minivmac">MiniVmac</a>
76+
<a class="dropdown-item" href="." data-core="mu">Mu</a>
77+
<a class="dropdown-item" href="." data-core="mupen64plus">Mupen64 Plus</a>
78+
<a class="dropdown-item" href="." data-core="mrboom">MrBoom</a>
79+
<a class="dropdown-item" href="." data-core="nestopia">Nestopia</a>
80+
<a class="dropdown-item" href="." data-core="nxengine">NX Engine</a>
81+
<a class="dropdown-item" href="." data-core="o2em">O2em</a>
82+
<a class="dropdown-item" href="." data-core="opera">Opera</a>
83+
<a class="dropdown-item" href="." data-core="picodrive">PicoDrive</a>
84+
<a class="dropdown-item" href="." data-core="prboom">PrBoom</a>
85+
<a class="dropdown-item" href="." data-core="quasi88">Quasi88</a>
86+
<a class="dropdown-item" href="." data-core="quicknes">QuickNES</a>
87+
<a class="dropdown-item" href="." data-core="retro8">Retro8</a>
88+
<a class="dropdown-item" href="." data-core="flycast">Flycast</a>
89+
<a class="dropdown-item" href="." data-core="snes9x2002">Snes9x 2002</a>
90+
<a class="dropdown-item" href="." data-core="snes9x2005">Snes9x 2005</a>
91+
<a class="dropdown-item" href="." data-core="snes9x2010">Snes9x 2010</a>
92+
<a class="dropdown-item" href="." data-core="snes9x">Snes9x</a>
93+
<a class="dropdown-item" href="." data-core="squirreljme">SquirrelJME</a>
94+
<a class="dropdown-item" href="." data-core="stella">Stella</a>
95+
<a class="dropdown-item" href="." data-core="tgbdual">TGB Dual</a>
96+
<a class="dropdown-item" href="." data-core="theodore">Theodore (Thomson TO8/TO9)</a>
97+
<a class="dropdown-item" href="." data-core="tic80">TIC-80</a>
98+
<a class="dropdown-item" href="." data-core="tyrquake">TyrQuake</a>
99+
<a class="dropdown-item" href="." data-core="uzem">UZEM</a>
100+
<a class="dropdown-item" href="." data-core="vaporspec">Vaporspec</a>
101+
<a class="dropdown-item" href="." data-core="vba_next">VBA Next</a>
102+
<a class="dropdown-item" href="." data-core="vecx">Vecx</a>
103+
<a class="dropdown-item" href="." data-core="vice_x64">VICE x64</a>
104+
<a class="dropdown-item" href="." data-core="vice_x64sc">VICE x64sc</a>
105+
<a class="dropdown-item" href="." data-core="vice_x128">VICE x128</a>
106+
<a class="dropdown-item" href="." data-core="vice_xcbm2">VICE xcbm2</a>
107+
<a class="dropdown-item" href="." data-core="vice_xcbm5x0">VICE xcbm5x0</a>
108+
<a class="dropdown-item" href="." data-core="vice_xpet">VICE xPET</a>
109+
<a class="dropdown-item" href="." data-core="vice_xplus4">VICE xPlus4</a>
110+
<a class="dropdown-item" href="." data-core="vice_xscpu64">VICE xscpu4</a>
111+
<a class="dropdown-item" href="." data-core="vice_xvic">VICE xVIC</a>
112+
<a class="dropdown-item" href="." data-core="vitaquake2">Vita Quake2</a>
113+
<a class="dropdown-item" href="." data-core="vitaquake2-rogue">Vita Quake2 (rogue)</a>
114+
<a class="dropdown-item" href="." data-core="vitaquake2-xatrix">Vita Quake2 (xatrix)</a>
115+
<a class="dropdown-item" href="." data-core="vitaquake2-zaero">Vita Quake2 (zaero)</a>
116+
<a class="dropdown-item" href="." data-core="virtualjaguar">Virtual Jaguar</a>
117+
<a class="dropdown-item" href="." data-core="wasm4">WASM4</a>
118+
<a class="dropdown-item" href="." data-core="x1">XMillenium</a>
119+
<a class="dropdown-item" href="." data-core="xrick">XRick</a>
120+
<a class="dropdown-item" href="." data-core="yabause">Yabause</a>
121+
</div>
122+
<button class="btn btn-primary disabled" id="btnRun" onclick="startRetroArch()" disabled>
123+
<span class="fa fa-spinner fa-spin" id="icnRun"></span> Run
124+
</button>
125+
<button class="btn btn-primary disabled" id="btnAdd" onclick="document.getElementById('btnRom').click()" disabled>
126+
<span class="fa fa-plus" id="icnAdd"></span> Add Content
127+
</button>
128+
<button class="btn btn-primary tooltip-enable" id="btnClean" onclick="cleanupStorage();" title="Cleanup storage">
129+
<span class="fa fa-trash-o" id="icnClean"></span> <span class="sr-only">Cleanup</span>
130+
</button>
131+
<input class="btn btn-primary disabled" style="display: none" type="file" id="btnRom" name="upload" onclick="document.getElementById('btnAdd').click();" onchange="selectFiles(event.target.files)" multiple />
132+
<button class="btn btn-primary disabled tooltip-enable" id="btnMenu" onclick="keyPress('F1');" title="Menu toggle" disabled>
133+
<span class="fa fa-bars" id="btnMenu"></span> <span class="sr-only">Menu</span>
134+
</button>
135+
<button class="btn btn-primary disabled tooltip-enable" id="btnFullscreen" onclick="Module.requestFullscreen(false)" title="Fullscreen" disabled>
136+
<span class="fa fa-desktop" id="icnAdd"></span> <span class="sr-only">Fullscreen</span>
137+
</button>
138+
</button>
139+
<button type="button" class="btn btn-primary tooltip-enable" data-toggle="modal" data-target="#helpModal">Help</button>
140+
</li>
141+
</div>
142+
</ul>
143+
<div class="toggleMenu">
144+
<button class="btn btn-primary" id="btnHideMenu" title="Toggle Menu">
145+
<span class="fa fa-chevron-up" id="icnHideMenu"></span> <span class="sr-only">Hide Top Navigation</span>
146+
</button>
147+
</div>
148+
</div>
149+
<!-- Basics steps modal for Web Libretro -->
150+
<div class="modal fade" id="helpModal" role="dialog" style="color:black;">
151+
<div class="modal-dialog modal-lg">
152+
<div class="modal-content">
153+
<div class="modal-header">
154+
<button type="button" class="close" data-dismiss="modal">&times;</button>
155+
<h1 class="modal-title">Basics</h1>
156+
</div>
157+
<div class="modal-body">
158+
<h3><b>Load Core</b></h3>
159+
<p>Load your core by clicking on the first tab. Scroll down until you reach the desired Core. We will use Nestopia for now. Don't forget - Content must be compatible with the matched Core.</p>
160+
<li>Nes: <i>NESTOPIA</i></li>
161+
<li>Game Boy / Color: <i>Gambatte</i></li>
162+
</ul>
163+
<p>etc.</p>
164+
<p></p>
165+
<h3><b>Load Content</b></h3>
166+
<p>After selecting Core, click Run. After RetroArch opens, click Add Content and select your compatible ROM.</p>
167+
<li>Nestopia > <i>YourGame.nes</i></li>
168+
<li>Gambatte > <i>YourGame.gbc</i></li>
169+
</ul>
170+
<p>etc.</p>
171+
<p></p>
172+
<h3><b><span class="fa fa-trash-o"></span> Cleanup Storage</b></h3>
173+
<p>The trashcan erases your existing configuration and presets. If the Web Player doesn't start, you should click the trashcan and refresh the cache in your browser (usually F5 or Shift+F5).</p>
174+
<p></p>
175+
<h3><b><span class="fa fa-bars"></span> Quick Menu</b></h3>
176+
<p>If you click on the three line icons, the Quick Menu will open here as in RetroArch.</p>
177+
178+
</div>
179+
<div class="modal-footer">
180+
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
181+
</div>
182+
</div>
183+
</div>
184+
</div>
185+
</div>
186+
<!--/.navbar content-->
187+
</div>
188+
</nav>
189+
<div class="bg-inverse webplayer-container">
190+
<div class="webplayer_border text-xs-center" id="canvas_div">
191+
<div class="showMenu">
192+
<button type="button" class="btn btn-link">
193+
<span class="fa fa-chevron-down" id="icnShowMenu"></span> <span class="sr-only">Show Top Navigation</span>
194+
</button>
195+
</div>
196+
<canvas class="webplayer" id="canvas" tabindex="1" oncontextmenu="event.preventDefault()" style="display: none"></canvas>
197+
<img class="webplayer-preview img-fluid" src="media/canvas.png" width="960px" height="720px" alt="RetroArch Logo">
198+
</div>
199+
</div>
200+
201+
<script crossorigin="anonymous" src="//code.jquery.com/jquery-3.1.0.min.js"></script>
202+
<script crossorigin="anonymous" src="//rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js"></script>
203+
<script crossorigin="anonymous" src="//cdnjs.cloudflare.com/ajax/libs/tether/1.3.4/js/tether.min.js"></script>
204+
<script crossorigin="anonymous" src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/js/bootstrap.min.js"></script>
205+
<script src="analytics.js"></script>
206+
<!--script src="//wzrd.in/standalone/[email protected]"></script-->
207+
<script src="browserfs.min.js"></script>
208+
<script src="libretro.js"></script>
209+
</body>
210+
</html>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#! /usr/bin/env coffee
2+
3+
fs = require 'fs'
4+
path = require 'path'
5+
6+
symLinks = {}
7+
8+
rdSync = (dpath, tree, name) ->
9+
files = fs.readdirSync(dpath)
10+
for file in files
11+
# ignore non-essential directories / files
12+
continue if file in ['.git', 'node_modules', 'bower_components', 'build'] or file[0] is '.'
13+
fpath = dpath + '/' + file
14+
try
15+
# Avoid infinite loops.
16+
lstat = fs.lstatSync(fpath)
17+
if lstat.isSymbolicLink()
18+
symLinks[lstat.dev] ?= {}
19+
# Ignore if we've seen it before
20+
continue if symLinks[lstat.dev][lstat.ino]?
21+
symLinks[lstat.dev][lstat.ino] = 0
22+
23+
fstat = fs.statSync(fpath)
24+
if fstat.isDirectory()
25+
tree[file] = child = {}
26+
rdSync(fpath, child, file)
27+
else
28+
tree[file] = null
29+
catch e
30+
# Ignore and move on.
31+
return tree
32+
33+
fs_listing = rdSync(process.cwd(), {}, '/')
34+
console.log(JSON.stringify(fs_listing))

0 commit comments

Comments
 (0)