loading improvements

This commit is contained in:
d07riv
2019-08-01 03:17:49 +03:00
parent 726b6c3ee0
commit 3097dd69cc
9 changed files with 118 additions and 42 deletions

View File

@@ -4,6 +4,7 @@ import classNames from 'classnames';
import create_fs from './fs';
import load_game from './api/loader';
import { SpawnSize } from './api/load_spawn';
function isDropFile(e) {
if (e.dataTransfer.items) {
@@ -37,7 +38,7 @@ const Link = ({children, ...props}) => <a target="_blank" rel="noopener noreferr
class App extends React.Component {
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};
touchButtons = [null, null, null, null, null, null];
@@ -63,6 +64,13 @@ class App extends React.Component {
document.addEventListener("dragover", this.onDragOver, true);
document.addEventListener("dragenter", this.onDragEnter, 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 => {
@@ -112,8 +120,8 @@ class App extends React.Component {
});
}
onProgress({type, loaded, total}) {
this.setState({progress: loaded / total});
onProgress(progress) {
this.setState({progress});
}
drawBelt(idx, slot) {
@@ -424,7 +432,7 @@ class App extends React.Component {
}
render() {
const {started, loading, error, progress, dropping, touch} = this.state;
const {started, loading, error, progress, dropping, touch, has_spawn} = this.state;
return (
<div className={classNames("App", {touch, started, dropping})} ref={this.setElement}>
<div className="touch-ui touch-mods">
@@ -449,8 +457,10 @@ class App extends React.Component {
)}
{!!loading && !started && !error && (
<div className="loading">
Loading...
{progress != null && <span className="progressBar"><span><span style={{width: `${Math.round(100 * progress)}%`}}/></span></span>}
{progress && progress.text || 'Loading...'}
{progress != null && !!progress.total && (
<span className="progressBar"><span><span style={{width: `${Math.round(100 * progress.loaded / progress.total)}%`}}/></span></span>
)}
</div>
)}
{!started && !loading && !error && (
@@ -466,10 +476,15 @@ class App extends React.Component {
</p>
<input accept=".mpq" type="file" id="loadFile" style={{display: "none"}} onChange={this.parseFile}/>
</form>
<p>
Or you can download and play the shareware version instead (50MB download).
</p>
<span className="startButton" onClick={() => this.start()}>Play Shareware</span>
{has_spawn ? (
<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>

View File

@@ -105,12 +105,12 @@ body, #root, .App {
.loading {
color: #888;
font-size: 48px;
font-size: 32px;
text-align: center;
width: 75%;
.progressBar {
display: block;
position: relative;
position: absolute;
margin-top: 16px;
width: 100%;
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.

View File

@@ -2,11 +2,16 @@ import DiabloBinary from './Diablo.wasm';
import DiabloModule from './Diablo.jscc';
import SpawnBinary from './DiabloSpawn.wasm';
import SpawnModule from './DiabloSpawn.jscc';
import axios from 'axios';
const DiabloSize = 1288646;
const SpawnSize = 1160666;
/* eslint-disable-next-line no-restricted-globals */
const worker = self;
let canvas = null, context = null;
let imageData = null;
let files = null;
let renderBatch = 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 = {
draw_begin() {
renderBatch = {
@@ -71,6 +87,7 @@ const DApi_renderLegacy = {
renderBatch.text.push({x, y, text, color});
},
draw_end() {
//DApi.draw_text(10, 10, `FPS: ${getFPS().toFixed(1)} (Transfer)`, 0xFFCC00);
const transfer = renderBatch.images.map(({data}) => data.buffer);
if (renderBatch.belt) {
transfer.push(renderBatch.belt.buffer);
@@ -89,9 +106,8 @@ const DApi_renderOffscreen = {
context.font = 'bold 13px Times New Roman';
},
draw_blit(x, y, w, h, data) {
const image = context.createImageData(w, h);
image.data.set(data);
context.putImageData(image, x, y);
imageData.data.set(data);
context.putImageData(imageData, x, y);
},
draw_clip_text(x0, y0, x1, y1) {
context.beginPath();
@@ -106,6 +122,7 @@ const DApi_renderOffscreen = {
context.fillText(text, x, y + 22);
},
draw_end() {
//DApi.draw_text(10, 10, `FPS: ${getFPS().toFixed(1)} (Offscreen)`, 0xFFCC00);
context.restore();
const bitmap = canvas.transferToImageBitmap();
const transfer = [bitmap];
@@ -166,35 +183,69 @@ function call_api(func, ...params) {
}
}
async function init_game(mpq, offscreen) {
if (mpq) {
/* eslint-disable-next-line no-undef */
const reader = new FileReaderSync();
const data = reader.readAsArrayBuffer(mpq);
files.set('diabdat.mpq', new Uint8Array(data));
}
function progress(text, loaded, total) {
worker.postMessage({action: "progress", text, loaded, total});
}
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) {
canvas = new OffscreenCanvas(640, 480);
context = canvas.getContext("2d");
imageData = context.createImageData(640, 480);
Object.assign(DApi, DApi_renderOffscreen);
} else {
Object.assign(DApi, DApi_renderLegacy);
}
wasm = await (mpq ? DiabloModule : SpawnModule)({
locateFile(name) {
if (name === 'DiabloSpawn.wasm') {
return SpawnBinary;
} else if (name === 'Diablo.wasm') {
return DiabloBinary;
} else {
return name;
}
}
}).ready;
progress("Loading...");
let mpqLoaded = 0, mpqTotal = (mpq ? mpq.size : 0), wasmLoaded = 0, wasmTotal = (spawn ? SpawnSize : DiabloSize);
const wasmWeight = 5;
function updateProgress() {
progress("Loading...", mpqLoaded + wasmLoaded * wasmWeight, mpqTotal + wasmTotal * wasmWeight);
}
const loadWasm = initWasm(spawn, e => {
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(() => {
call_api("DApi_Render", Math.floor(performance.now()));
@@ -205,7 +256,7 @@ worker.addEventListener("message", ({data}) => {
switch (data.action) {
case "init":
files = data.files;
init_game(data.mpq, data.offscreen).then(
init_game(data.mpq, data.spawn, data.offscreen).then(
() => worker.postMessage({action: "loaded"}),
e => {debugger;worker.postMessage({action: "failed", error: e.message || e.name});});
break;

View File

@@ -1,7 +1,9 @@
import axios from 'axios';
//import axios from 'axios';
const SpawnSize = 50274091;
export { SpawnSize };
export default async function load_spawn(api, fs) {
let file = fs.files.get('spawn.mpq');
if (file && file.byteLength !== SpawnSize) {
@@ -10,7 +12,8 @@ export default async function load_spawn(api, fs) {
file = null;
}
if (!file) {
const spawn = await axios.request({
throw Error("Invalid spawn.mpq size.");
/*const spawn = await axios.request({
url: '/spawn.mpq',
responseType: 'arraybuffer',
onDownloadProgress: e => {
@@ -27,7 +30,7 @@ export default async function load_spawn(api, fs) {
}
const data = new Uint8Array(spawn.data);
fs.files.set('spawn.mpq', data);
fs.update('spawn.mpq', data);
fs.update('spawn.mpq', data);*/
}
return fs;
}

View File

@@ -48,8 +48,12 @@ function testOffscreen() {
async function do_load_game(api, audio, mpq) {
const fs = await api.fs;
let spawn = true;
if (mpq) {
fs.files.delete('spawn.mpq');
if (!mpq.name.match(/^spawn\.mpq$/i)) {
spawn = false;
fs.files.delete('spawn.mpq');
}
} else {
await load_spawn(api, fs);
}
@@ -95,6 +99,9 @@ async function do_load_game(api, audio, mpq) {
case "failed":
reject(Error(data.error));
break;
case "progress":
api.onProgress({text: data.text, loaded: data.loaded, total: data.total});
break;
default:
}
});
@@ -102,7 +109,7 @@ async function do_load_game(api, audio, mpq) {
for (let [name, file] of fs.files) {
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;
} catch (e) {
reject(e);