mirror of
https://github.com/d07RiV/diabloweb.git
synced 2026-06-03 21:41:38 +00:00
loading improvements
This commit is contained in:
35
src/App.js
35
src/App.js
@@ -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>
|
||||
|
||||
@@ -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.
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user