mirror of
https://github.com/d07RiV/diabloweb.git
synced 2026-07-03 20:01:34 +00:00
loading improvements
This commit is contained in:
33
src/App.js
33
src/App.js
@@ -4,6 +4,7 @@ import classNames from 'classnames';
|
|||||||
|
|
||||||
import create_fs from './fs';
|
import create_fs from './fs';
|
||||||
import load_game from './api/loader';
|
import load_game from './api/loader';
|
||||||
|
import { SpawnSize } from './api/load_spawn';
|
||||||
|
|
||||||
function isDropFile(e) {
|
function isDropFile(e) {
|
||||||
if (e.dataTransfer.items) {
|
if (e.dataTransfer.items) {
|
||||||
@@ -37,7 +38,7 @@ const Link = ({children, ...props}) => <a target="_blank" rel="noopener noreferr
|
|||||||
|
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
files = new Map();
|
files = new Map();
|
||||||
state = {started: false, loading: false, touch: false, dropping: 0};
|
state = {started: false, loading: false, touch: false, dropping: 0, has_spawn: false};
|
||||||
cursorPos = {x: 0, y: 0};
|
cursorPos = {x: 0, y: 0};
|
||||||
|
|
||||||
touchButtons = [null, null, null, null, null, null];
|
touchButtons = [null, null, null, null, null, null];
|
||||||
@@ -63,6 +64,13 @@ class App extends React.Component {
|
|||||||
document.addEventListener("dragover", this.onDragOver, true);
|
document.addEventListener("dragover", this.onDragOver, true);
|
||||||
document.addEventListener("dragenter", this.onDragEnter, true);
|
document.addEventListener("dragenter", this.onDragEnter, true);
|
||||||
document.addEventListener("dragleave", this.onDragLeave, true);
|
document.addEventListener("dragleave", this.onDragLeave, true);
|
||||||
|
|
||||||
|
this.fs.then(fs => {
|
||||||
|
const spawn = fs.files.get('spawn.mpq');
|
||||||
|
if (spawn && spawn.byteLength === SpawnSize) {
|
||||||
|
this.setState({has_spawn: true});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onDrop = e => {
|
onDrop = e => {
|
||||||
@@ -112,8 +120,8 @@ class App extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgress({type, loaded, total}) {
|
onProgress(progress) {
|
||||||
this.setState({progress: loaded / total});
|
this.setState({progress});
|
||||||
}
|
}
|
||||||
|
|
||||||
drawBelt(idx, slot) {
|
drawBelt(idx, slot) {
|
||||||
@@ -424,7 +432,7 @@ class App extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {started, loading, error, progress, dropping, touch} = this.state;
|
const {started, loading, error, progress, dropping, touch, has_spawn} = this.state;
|
||||||
return (
|
return (
|
||||||
<div className={classNames("App", {touch, started, dropping})} ref={this.setElement}>
|
<div className={classNames("App", {touch, started, dropping})} ref={this.setElement}>
|
||||||
<div className="touch-ui touch-mods">
|
<div className="touch-ui touch-mods">
|
||||||
@@ -449,8 +457,10 @@ class App extends React.Component {
|
|||||||
)}
|
)}
|
||||||
{!!loading && !started && !error && (
|
{!!loading && !started && !error && (
|
||||||
<div className="loading">
|
<div className="loading">
|
||||||
Loading...
|
{progress && progress.text || 'Loading...'}
|
||||||
{progress != null && <span className="progressBar"><span><span style={{width: `${Math.round(100 * progress)}%`}}/></span></span>}
|
{progress != null && !!progress.total && (
|
||||||
|
<span className="progressBar"><span><span style={{width: `${Math.round(100 * progress.loaded / progress.total)}%`}}/></span></span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!started && !loading && !error && (
|
{!started && !loading && !error && (
|
||||||
@@ -466,10 +476,15 @@ class App extends React.Component {
|
|||||||
</p>
|
</p>
|
||||||
<input accept=".mpq" type="file" id="loadFile" style={{display: "none"}} onChange={this.parseFile}/>
|
<input accept=".mpq" type="file" id="loadFile" style={{display: "none"}} onChange={this.parseFile}/>
|
||||||
</form>
|
</form>
|
||||||
<p>
|
{has_spawn ? (
|
||||||
Or you can download and play the shareware version instead (50MB download).
|
|
||||||
</p>
|
|
||||||
<span className="startButton" onClick={() => this.start()}>Play Shareware</span>
|
<span className="startButton" onClick={() => this.start()}>Play Shareware</span>
|
||||||
|
) : (
|
||||||
|
<p>
|
||||||
|
Or you can download and play the shareware version instead (50MB download). <i>The site has lately been under too much stress due to users downloading the shareware
|
||||||
|
package, so instead you will need to <a href="https://d07riv.github.io/diabloweb/public/spawn.mpq">download it from GitHub</a>, then drop it on the page (or upload
|
||||||
|
by <label htmlFor="loadFile" className="link" onClick={this.download}>clicking here</label>).</i>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -105,12 +105,12 @@ body, #root, .App {
|
|||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
color: #888;
|
color: #888;
|
||||||
font-size: 48px;
|
font-size: 32px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 75%;
|
width: 75%;
|
||||||
.progressBar {
|
.progressBar {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: absolute;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -2,11 +2,16 @@ import DiabloBinary from './Diablo.wasm';
|
|||||||
import DiabloModule from './Diablo.jscc';
|
import DiabloModule from './Diablo.jscc';
|
||||||
import SpawnBinary from './DiabloSpawn.wasm';
|
import SpawnBinary from './DiabloSpawn.wasm';
|
||||||
import SpawnModule from './DiabloSpawn.jscc';
|
import SpawnModule from './DiabloSpawn.jscc';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const DiabloSize = 1288646;
|
||||||
|
const SpawnSize = 1160666;
|
||||||
|
|
||||||
/* eslint-disable-next-line no-restricted-globals */
|
/* eslint-disable-next-line no-restricted-globals */
|
||||||
const worker = self;
|
const worker = self;
|
||||||
|
|
||||||
let canvas = null, context = null;
|
let canvas = null, context = null;
|
||||||
|
let imageData = null;
|
||||||
let files = null;
|
let files = null;
|
||||||
let renderBatch = null;
|
let renderBatch = null;
|
||||||
let drawBelt = null;
|
let drawBelt = null;
|
||||||
@@ -51,6 +56,17 @@ const DApi = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let frameTime = 0, lastTime = 0;
|
||||||
|
function getFPS() {
|
||||||
|
const time = performance.now();
|
||||||
|
if (!lastTime) {
|
||||||
|
lastTime = time;
|
||||||
|
}
|
||||||
|
frameTime = 0.9 * frameTime + 0.1 * (time - lastTime);
|
||||||
|
lastTime = time;
|
||||||
|
return frameTime ? 1000.0 / frameTime : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
const DApi_renderLegacy = {
|
const DApi_renderLegacy = {
|
||||||
draw_begin() {
|
draw_begin() {
|
||||||
renderBatch = {
|
renderBatch = {
|
||||||
@@ -71,6 +87,7 @@ const DApi_renderLegacy = {
|
|||||||
renderBatch.text.push({x, y, text, color});
|
renderBatch.text.push({x, y, text, color});
|
||||||
},
|
},
|
||||||
draw_end() {
|
draw_end() {
|
||||||
|
//DApi.draw_text(10, 10, `FPS: ${getFPS().toFixed(1)} (Transfer)`, 0xFFCC00);
|
||||||
const transfer = renderBatch.images.map(({data}) => data.buffer);
|
const transfer = renderBatch.images.map(({data}) => data.buffer);
|
||||||
if (renderBatch.belt) {
|
if (renderBatch.belt) {
|
||||||
transfer.push(renderBatch.belt.buffer);
|
transfer.push(renderBatch.belt.buffer);
|
||||||
@@ -89,9 +106,8 @@ const DApi_renderOffscreen = {
|
|||||||
context.font = 'bold 13px Times New Roman';
|
context.font = 'bold 13px Times New Roman';
|
||||||
},
|
},
|
||||||
draw_blit(x, y, w, h, data) {
|
draw_blit(x, y, w, h, data) {
|
||||||
const image = context.createImageData(w, h);
|
imageData.data.set(data);
|
||||||
image.data.set(data);
|
context.putImageData(imageData, x, y);
|
||||||
context.putImageData(image, x, y);
|
|
||||||
},
|
},
|
||||||
draw_clip_text(x0, y0, x1, y1) {
|
draw_clip_text(x0, y0, x1, y1) {
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
@@ -106,6 +122,7 @@ const DApi_renderOffscreen = {
|
|||||||
context.fillText(text, x, y + 22);
|
context.fillText(text, x, y + 22);
|
||||||
},
|
},
|
||||||
draw_end() {
|
draw_end() {
|
||||||
|
//DApi.draw_text(10, 10, `FPS: ${getFPS().toFixed(1)} (Offscreen)`, 0xFFCC00);
|
||||||
context.restore();
|
context.restore();
|
||||||
const bitmap = canvas.transferToImageBitmap();
|
const bitmap = canvas.transferToImageBitmap();
|
||||||
const transfer = [bitmap];
|
const transfer = [bitmap];
|
||||||
@@ -166,35 +183,69 @@ function call_api(func, ...params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init_game(mpq, offscreen) {
|
function progress(text, loaded, total) {
|
||||||
if (mpq) {
|
worker.postMessage({action: "progress", text, loaded, total});
|
||||||
/* eslint-disable-next-line no-undef */
|
}
|
||||||
const reader = new FileReaderSync();
|
|
||||||
const data = reader.readAsArrayBuffer(mpq);
|
|
||||||
files.set('diabdat.mpq', new Uint8Array(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const readFile = (file, progress) => new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
if (progress) {
|
||||||
|
progress({loaded: file.size});
|
||||||
|
}
|
||||||
|
resolve(reader.result);
|
||||||
|
},
|
||||||
|
reader.onerror = () => reject(reader.error);
|
||||||
|
reader.onabort = () => reject();
|
||||||
|
if (progress) {
|
||||||
|
reader.addEventListener("progress", progress);
|
||||||
|
}
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function initWasm(spawn, progress) {
|
||||||
|
const binary = await axios.request({
|
||||||
|
url: spawn ? SpawnBinary : DiabloBinary,
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
onDownloadProgress: progress,
|
||||||
|
});
|
||||||
|
const result = await (spawn ? SpawnModule : DiabloModule)({wasmBinary: binary.data}).ready;
|
||||||
|
progress({loaded: 2000000});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init_game(mpq, spawn, offscreen) {
|
||||||
if (offscreen) {
|
if (offscreen) {
|
||||||
canvas = new OffscreenCanvas(640, 480);
|
canvas = new OffscreenCanvas(640, 480);
|
||||||
context = canvas.getContext("2d");
|
context = canvas.getContext("2d");
|
||||||
|
imageData = context.createImageData(640, 480);
|
||||||
Object.assign(DApi, DApi_renderOffscreen);
|
Object.assign(DApi, DApi_renderOffscreen);
|
||||||
} else {
|
} else {
|
||||||
Object.assign(DApi, DApi_renderLegacy);
|
Object.assign(DApi, DApi_renderLegacy);
|
||||||
}
|
}
|
||||||
|
|
||||||
wasm = await (mpq ? DiabloModule : SpawnModule)({
|
progress("Loading...");
|
||||||
locateFile(name) {
|
let mpqLoaded = 0, mpqTotal = (mpq ? mpq.size : 0), wasmLoaded = 0, wasmTotal = (spawn ? SpawnSize : DiabloSize);
|
||||||
if (name === 'DiabloSpawn.wasm') {
|
const wasmWeight = 5;
|
||||||
return SpawnBinary;
|
function updateProgress() {
|
||||||
} else if (name === 'Diablo.wasm') {
|
progress("Loading...", mpqLoaded + wasmLoaded * wasmWeight, mpqTotal + wasmTotal * wasmWeight);
|
||||||
return DiabloBinary;
|
|
||||||
} else {
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
}
|
const loadWasm = initWasm(spawn, e => {
|
||||||
}).ready;
|
wasmLoaded = Math.min(e.loaded, wasmTotal);
|
||||||
|
updateProgress();
|
||||||
|
});
|
||||||
|
let loadMpq = mpq ? readFile(mpq, e => {
|
||||||
|
mpqLoaded = e.loaded;
|
||||||
|
updateProgress();
|
||||||
|
}) : Promise.resolve(null);
|
||||||
|
[wasm, mpq] = await Promise.all([loadWasm, loadMpq]);
|
||||||
|
|
||||||
wasm._DApi_Init(Math.floor(performance.now()));
|
if (mpq) {
|
||||||
|
files.set(spawn ? 'spawn.mpq' : 'diabdat.mpq', new Uint8Array(mpq));
|
||||||
|
}
|
||||||
|
|
||||||
|
progress("Initializing...");
|
||||||
|
wasm._DApi_Init(Math.floor(performance.now()), offscreen ? 1 : 0);
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
call_api("DApi_Render", Math.floor(performance.now()));
|
call_api("DApi_Render", Math.floor(performance.now()));
|
||||||
@@ -205,7 +256,7 @@ worker.addEventListener("message", ({data}) => {
|
|||||||
switch (data.action) {
|
switch (data.action) {
|
||||||
case "init":
|
case "init":
|
||||||
files = data.files;
|
files = data.files;
|
||||||
init_game(data.mpq, data.offscreen).then(
|
init_game(data.mpq, data.spawn, data.offscreen).then(
|
||||||
() => worker.postMessage({action: "loaded"}),
|
() => worker.postMessage({action: "loaded"}),
|
||||||
e => {debugger;worker.postMessage({action: "failed", error: e.message || e.name});});
|
e => {debugger;worker.postMessage({action: "failed", error: e.message || e.name});});
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import axios from 'axios';
|
//import axios from 'axios';
|
||||||
|
|
||||||
const SpawnSize = 50274091;
|
const SpawnSize = 50274091;
|
||||||
|
|
||||||
|
export { SpawnSize };
|
||||||
|
|
||||||
export default async function load_spawn(api, fs) {
|
export default async function load_spawn(api, fs) {
|
||||||
let file = fs.files.get('spawn.mpq');
|
let file = fs.files.get('spawn.mpq');
|
||||||
if (file && file.byteLength !== SpawnSize) {
|
if (file && file.byteLength !== SpawnSize) {
|
||||||
@@ -10,7 +12,8 @@ export default async function load_spawn(api, fs) {
|
|||||||
file = null;
|
file = null;
|
||||||
}
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
const spawn = await axios.request({
|
throw Error("Invalid spawn.mpq size.");
|
||||||
|
/*const spawn = await axios.request({
|
||||||
url: '/spawn.mpq',
|
url: '/spawn.mpq',
|
||||||
responseType: 'arraybuffer',
|
responseType: 'arraybuffer',
|
||||||
onDownloadProgress: e => {
|
onDownloadProgress: e => {
|
||||||
@@ -27,7 +30,7 @@ export default async function load_spawn(api, fs) {
|
|||||||
}
|
}
|
||||||
const data = new Uint8Array(spawn.data);
|
const data = new Uint8Array(spawn.data);
|
||||||
fs.files.set('spawn.mpq', data);
|
fs.files.set('spawn.mpq', data);
|
||||||
fs.update('spawn.mpq', data);
|
fs.update('spawn.mpq', data);*/
|
||||||
}
|
}
|
||||||
return fs;
|
return fs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,12 @@ function testOffscreen() {
|
|||||||
|
|
||||||
async function do_load_game(api, audio, mpq) {
|
async function do_load_game(api, audio, mpq) {
|
||||||
const fs = await api.fs;
|
const fs = await api.fs;
|
||||||
|
let spawn = true;
|
||||||
if (mpq) {
|
if (mpq) {
|
||||||
|
if (!mpq.name.match(/^spawn\.mpq$/i)) {
|
||||||
|
spawn = false;
|
||||||
fs.files.delete('spawn.mpq');
|
fs.files.delete('spawn.mpq');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await load_spawn(api, fs);
|
await load_spawn(api, fs);
|
||||||
}
|
}
|
||||||
@@ -95,6 +99,9 @@ async function do_load_game(api, audio, mpq) {
|
|||||||
case "failed":
|
case "failed":
|
||||||
reject(Error(data.error));
|
reject(Error(data.error));
|
||||||
break;
|
break;
|
||||||
|
case "progress":
|
||||||
|
api.onProgress({text: data.text, loaded: data.loaded, total: data.total});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -102,7 +109,7 @@ async function do_load_game(api, audio, mpq) {
|
|||||||
for (let [name, file] of fs.files) {
|
for (let [name, file] of fs.files) {
|
||||||
transfer.push(file.buffer);
|
transfer.push(file.buffer);
|
||||||
}
|
}
|
||||||
worker.postMessage({action: "init", files: fs.files, mpq, offscreen}, transfer);
|
worker.postMessage({action: "init", files: fs.files, mpq, spawn, offscreen}, transfer);
|
||||||
delete fs.files;
|
delete fs.files;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
|
|||||||
Reference in New Issue
Block a user