Changed asset loading bar to show progress as a group of assets

This commit is contained in:
Mitchell McCaffrey 2021-04-29 13:49:39 +10:00
parent 994b9b5ebb
commit 0ccee84cbf
4 changed files with 77 additions and 64 deletions

View File

@ -1,46 +1,44 @@
import React, { useState, useRef, useContext } from "react";
import { omit, isEmpty } from "../helpers/shared";
import React, { useState, useRef, useContext, useCallback } from "react";
const MapLoadingContext = React.createContext();
export function MapLoadingProvider({ children }) {
const [loadingAssetCount, setLoadingAssetCount] = useState(0);
function assetLoadStart() {
setLoadingAssetCount((prevLoadingAssets) => prevLoadingAssets + 1);
}
function assetLoadFinish() {
setLoadingAssetCount((prevLoadingAssets) => prevLoadingAssets - 1);
}
const [isLoading, setIsLoading] = useState(false);
// Mapping from asset id to the count and total number of pieces loaded
const assetProgressRef = useRef({});
// Loading progress of all assets between 0 and 1
const loadingProgressRef = useRef(null);
function assetProgressUpdate({ id, count, total }) {
if (count === total) {
assetProgressRef.current = omit(assetProgressRef.current, [id]);
} else {
assetProgressRef.current = {
...assetProgressRef.current,
[id]: { count, total },
};
}
if (!isEmpty(assetProgressRef.current)) {
let total = 0;
let count = 0;
for (let progress of Object.values(assetProgressRef.current)) {
total += progress.total;
count += progress.count;
}
loadingProgressRef.current = count / total;
}
}
const isLoading = loadingAssetCount > 0;
const assetLoadStart = useCallback((id) => {
setIsLoading(true);
// Add asset at a 0% progress
assetProgressRef.current = {
...assetProgressRef.current,
[id]: { count: 0, total: 1 },
};
}, []);
const assetProgressUpdate = useCallback(({ id, count, total }) => {
assetProgressRef.current = {
...assetProgressRef.current,
[id]: { count, total },
};
// Update loading progress
let complete = 0;
const progresses = Object.values(assetProgressRef.current);
for (let progress of progresses) {
complete += progress.count / progress.total;
}
loadingProgressRef.current = complete / progresses.length;
// All loading is complete
if (loadingProgressRef.current === 1) {
setIsLoading(false);
assetProgressRef.current = {};
}
}, []);
const value = {
assetLoadStart,
assetLoadFinish,
isLoading,
assetProgressUpdate,
loadingProgressRef,

View File

@ -55,13 +55,18 @@ class Connection extends SimplePeer {
}
}
// Custom send function with encoding, chunking and data channel support
// Uses `write` to send the data to allow for buffer / backpressure handling
sendObject(object, channel) {
/**
* Custom send function with encoding, chunking and data channel support
* Uses `write` to send the data to allow for buffer / backpressure handling
* @param {any} object
* @param {string=} channel
* @param {string=} chunkId Optional ID to use for chunking
*/
sendObject(object, channel, chunkId) {
try {
const packedData = encode(object);
if (packedData.byteLength > MAX_BUFFER_SIZE) {
const chunks = this.chunk(packedData);
const chunks = this.chunk(packedData, chunkId);
for (let chunk of chunks) {
if (this.dataChannels[channel]) {
this.dataChannels[channel].write(encode(chunk));
@ -100,11 +105,17 @@ class Connection extends SimplePeer {
}
// Converted from https://github.com/peers/peerjs/
chunk(data) {
/**
* Chunk byte array
* @param {Uint8Array} data
* @param {string=} chunkId
* @returns {Uint8Array[]}
*/
chunk(data, chunkId) {
const chunks = [];
const size = data.byteLength;
const total = Math.ceil(size / MAX_BUFFER_SIZE);
const id = shortid.generate();
const id = chunkId || shortid.generate();
let index = 0;
let start = 0;

View File

@ -39,12 +39,7 @@ function NetworkedMapAndTokens({ session }) {
const { addToast } = useToasts();
const { userId } = useAuth();
const partyState = useParty();
const {
assetLoadStart,
assetLoadFinish,
assetProgressUpdate,
isLoading,
} = useMapLoading();
const { assetLoadStart, assetProgressUpdate, isLoading } = useMapLoading();
const { updateMapState } = useMapData();
const { getAsset, putAsset } = useAssets();
@ -115,7 +110,7 @@ function NetworkedMapAndTokens({ session }) {
const requestingAssetsRef = useRef(new Set());
useEffect(() => {
if (!assetManifest) {
if (!assetManifest || !userId) {
return;
}
@ -132,6 +127,9 @@ function NetworkedMapAndTokens({ session }) {
(player) => player.userId === asset.owner
);
// Ensure requests are added before any async operation to prevent them from sending twice
requestingAssetsRef.current.add(asset.id);
const cachedAsset = await getAsset(asset.id);
if (!owner) {
// Add no owner toast if we don't have asset in out cache
@ -139,21 +137,29 @@ function NetworkedMapAndTokens({ session }) {
// TODO: Stop toast from appearing multiple times
addToast("Unable to find owner for asset");
}
requestingAssetsRef.current.delete(asset.id);
continue;
}
requestingAssetsRef.current.add(asset.id);
if (cachedAsset) {
requestingAssetsRef.current.delete(asset.id);
} else {
session.sendTo(owner.sessionId, "assetRequest", asset.id);
assetLoadStart(asset.id);
}
}
}
requestAssetsIfNeeded();
}, [assetManifest, partyState, session, userId, addToast, getAsset]);
}, [
assetManifest,
partyState,
session,
userId,
addToast,
getAsset,
assetLoadStart,
]);
/**
* Map state
@ -376,21 +382,16 @@ function NetworkedMapAndTokens({ session }) {
async function handlePeerData({ id, data, reply }) {
if (id === "assetRequest") {
const asset = await getAsset(data);
reply("assetResponse", asset);
reply("assetResponse", asset, undefined, asset.id);
}
if (id === "assetResponse") {
await putAsset(data);
requestingAssetsRef.current.delete(data.id);
assetLoadFinish();
}
}
function handlePeerDataProgress({ id, total, count }) {
if (count === 1) {
// Corresponding asset load finished called in asset response
assetLoadStart();
}
assetProgressUpdate({ id, total, count });
}

View File

@ -19,7 +19,8 @@ import { logError } from "../helpers/logging";
* @callback peerReply
* @param {string} id - The id of the event
* @param {object} data - The data to send
* @param {string} channel - The channel to send to
* @param {string=} channel - The channel to send to
* @param {string=} chunkId
*/
/**
@ -120,9 +121,10 @@ class Session extends EventEmitter {
* @param {string} sessionId - The socket id of the player to send to
* @param {string} eventId - The id of the event to send
* @param {object} data
* @param {string} channel
* @param {string=} channel
* @param {string=} chunkId
*/
sendTo(sessionId, eventId, data, channel) {
sendTo(sessionId, eventId, data, channel, chunkId) {
if (!(sessionId in this.peers)) {
if (!this._addPeer(sessionId, true)) {
return;
@ -133,7 +135,8 @@ class Session extends EventEmitter {
this.peers[sessionId].connection.once("connect", () => {
this.peers[sessionId].connection.sendObject(
{ id: eventId, data },
channel
channel,
chunkId
);
});
} else {
@ -226,8 +229,8 @@ class Session extends EventEmitter {
const peer = { id, connection, initiator, ready: false };
function sendPeer(id, data, channel) {
peer.connection.sendObject({ id, data }, channel);
function reply(id, data, channel, chunkId) {
peer.connection.sendObject({ id, data }, channel, chunkId);
}
function handleSignal(signal) {
@ -246,7 +249,7 @@ class Session extends EventEmitter {
* @property {SessionPeer} peer
* @property {peerReply} reply
*/
this.emit("peerConnect", { peer, reply: sendPeer });
this.emit("peerConnect", { peer, reply });
}
function handleDataComplete(data) {
@ -264,7 +267,7 @@ class Session extends EventEmitter {
peer,
id: data.id,
data: data.data,
reply: sendPeer,
reply,
});
}
@ -274,7 +277,7 @@ class Session extends EventEmitter {
id,
count,
total,
reply: sendPeer,
reply,
});
}